From 5636629bf77a996115e7a244d584e4864c08a49c Mon Sep 17 00:00:00 2001 From: ssenior Date: Thu, 26 Mar 2020 15:18:28 -0400 Subject: [PATCH] v0.8.0-beta Initial library baseline. --- CHANGELOG.md | 33 + CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 6 +- NOTICE | 48 +- README.md | 61 +- source/lerna.json | 11 + source/package.json | 44 + .../aws-apigateway-dynamodb/.eslintignore | 5 + .../aws-apigateway-dynamodb/.gitignore | 16 + .../aws-apigateway-dynamodb/.npmignore | 21 + .../aws-apigateway-dynamodb/README.md | 77 + .../aws-apigateway-dynamodb/architecture.png | Bin 0 -> 71973 bytes .../aws-apigateway-dynamodb/lib/index.ts | 226 ++ .../aws-apigateway-dynamodb/package.json | 79 + .../apigateway-dynamodb.test.js.snap | 361 +++ .../test/apigateway-dynamodb.test.ts | 192 ++ ...teg.apigateway-dynamodb-CRUD.expected.json | 635 +++++ .../test/integ.apigateway-dynamodb-CRUD.ts | 36 + .../test/integ.no-arguments.expected.json | 350 +++ .../test/integ.no-arguments.ts | 30 + .../aws-apigateway-lambda/.eslintignore | 5 + .../aws-apigateway-lambda/.gitignore | 15 + .../aws-apigateway-lambda/.npmignore | 21 + .../aws-apigateway-lambda/README.md | 78 + .../aws-apigateway-lambda/architecture.png | Bin 0 -> 98366 bytes .../aws-apigateway-lambda/lib/index.ts | 122 + .../aws-apigateway-lambda/package.json | 79 + .../test.apigateway-lambda.test.js.snap | 1136 +++++++++ .../test/integ.deployFunction.expected.json | 583 +++++ .../test/integ.deployFunction.ts | 37 + .../test/integ.existingFunction.expected.json | 583 +++++ .../test/integ.existingFunction.ts | 44 + .../test/lambda/index.js | 21 + .../test/test.apigateway-lambda.test.ts | 138 ++ .../aws-apigateway-sqs/.eslintignore | 5 + .../aws-apigateway-sqs/.gitignore | 15 + .../aws-apigateway-sqs/.npmignore | 21 + .../aws-apigateway-sqs/README.md | 86 + .../aws-apigateway-sqs/architecture.png | Bin 0 -> 98229 bytes .../aws-apigateway-sqs/lib/index.ts | 240 ++ .../aws-apigateway-sqs/package.json | 80 + .../__snapshots__/apigateway-sqs.test.js.snap | 1720 +++++++++++++ .../test/apigateway-sqs.test.ts | 108 + .../integ.apigateway-sqs-crud.expected.json | 634 +++++ .../test/integ.apigateway-sqs-crud.ts | 37 + .../test/integ.no-arguments.expected.json | 445 ++++ .../test/integ.no-arguments.ts | 33 + .../.eslintignore | 5 + .../.gitignore | 16 + .../.npmignore | 21 + .../README.md | 82 + .../architecture.png | Bin 0 -> 127105 bytes .../lib/index.ts | 121 + .../package.json | 83 + ....cloudfront-apigateway-lambda.test.js.snap | 720 ++++++ .../test/integ.no-arguments.expected.json | 717 ++++++ .../test/integ.no-arguments.ts | 36 + .../test/lambda/index.js | 10 + .../test.cloudfront-apigateway-lambda.test.ts | 179 ++ .../aws-cloudfront-apigateway/.eslintignore | 5 + .../aws-cloudfront-apigateway/.gitignore | 16 + .../aws-cloudfront-apigateway/.npmignore | 21 + .../aws-cloudfront-apigateway/README.md | 82 + .../architecture.png | Bin 0 -> 98447 bytes .../aws-cloudfront-apigateway/lib/index.ts | 77 + .../aws-cloudfront-apigateway/package.json | 81 + .../test.cloudfront-apigateway.test.js.snap | 720 ++++++ .../test/integ.no-arguments.expected.json | 717 ++++++ .../test/integ.no-arguments.ts | 40 + .../test/lambda/index.js | 23 + .../test/test.cloudfront-apigateway.test.ts | 160 ++ .../aws-cloudfront-s3/.eslintignore | 5 + .../aws-cloudfront-s3/.gitignore | 16 + .../aws-cloudfront-s3/.npmignore | 21 + .../aws-cloudfront-s3/README.md | 71 + .../aws-cloudfront-s3/architecture.png | Bin 0 -> 102379 bytes .../aws-cloudfront-s3/lib/index.ts | 95 + .../aws-cloudfront-s3/package.json | 77 + .../test.cloudfront-s3.test.js.snap | 289 +++ .../test/integ.no-arguments.expected.json | 286 +++ .../test/integ.no-arguments.ts | 31 + .../test/test.cloudfront-s3.test.ts | 129 + .../.eslintignore | 5 + .../aws-cognito-apigateway-lambda/.gitignore | 16 + .../aws-cognito-apigateway-lambda/.npmignore | 21 + .../aws-cognito-apigateway-lambda/README.md | 83 + .../architecture.png | Bin 0 -> 83951 bytes .../lib/index.ts | 150 ++ .../package.json | 79 + ...est.cognito-apigateway-lambda.test.js.snap | 620 +++++ .../test/integ.no-arguments.expected.json | 616 +++++ .../test/integ.no-arguments.ts | 35 + .../test/lambda/index.js | 10 + .../test.cognito-apigateway-lambda.test.ts | 96 + .../.eslintignore | 5 + .../.gitignore | 16 + .../.npmignore | 21 + .../README.md | 86 + .../architecture.png | Bin 0 -> 127731 bytes .../lib/index.ts | 182 ++ .../package.json | 89 + ...m-lambda-elasticsearch-kibana.test.js.snap | 681 ++++++ ...stream-lambda-elasticsearch-kibana.test.ts | 88 + .../test/integ.no-arguments.expected.json | 677 +++++ .../test/integ.no-arguments.ts | 34 + .../test/lambda/index.js | 68 + .../aws-dynamodb-stream-lambda/.eslintignore | 5 + .../aws-dynamodb-stream-lambda/.gitignore | 16 + .../aws-dynamodb-stream-lambda/.npmignore | 21 + .../aws-dynamodb-stream-lambda/README.md | 79 + .../architecture.png | Bin 0 -> 71390 bytes .../aws-dynamodb-stream-lambda/lib/index.ts | 117 + .../aws-dynamodb-stream-lambda/package.json | 79 + .../dynamodb-stream-lambda.test.js.snap | 233 ++ .../test/dynamodb-stream-lambda.test.ts | 206 ++ .../test/integ.no-arguments.expected.json | 229 ++ .../test/integ.no-arguments.ts | 33 + .../test/lambda/index.js | 8 + .../aws-events-rule-lambda/.eslintignore | 5 + .../aws-events-rule-lambda/.gitignore | 16 + .../aws-events-rule-lambda/.npmignore | 21 + .../aws-events-rule-lambda/README.md | 80 + .../aws-events-rule-lambda/architecture.png | Bin 0 -> 53264 bytes .../aws-events-rule-lambda/lib/index.ts | 112 + .../aws-events-rule-lambda/package.json | 79 + .../events-rule-lambda.test.js.snap | 179 ++ .../test/events-rule-lambda.test.ts | 189 ++ ...nteg.events-rule-no-argument.expected.json | 175 ++ .../test/integ.events-rule-no-argument.ts | 37 + .../test/lambda/index.js | 10 + .../aws-iot-kinesisfirehose-s3/.eslintignore | 5 + .../aws-iot-kinesisfirehose-s3/.gitignore | 16 + .../aws-iot-kinesisfirehose-s3/.npmignore | 21 + .../aws-iot-kinesisfirehose-s3/README.md | 82 + .../architecture.png | Bin 0 -> 131500 bytes .../aws-iot-kinesisfirehose-s3/lib/index.ts | 146 ++ .../aws-iot-kinesisfirehose-s3/package.json | 82 + .../test.iot-kinesisfirehose-s3.test.js.snap | 297 +++ .../test/integ.no-arguments.expected.json | 294 +++ .../test/integ.no-arguments.ts | 38 + .../test/test.iot-kinesisfirehose-s3.test.ts | 126 + .../aws-iot-lambda-dynamodb/.eslintignore | 5 + .../aws-iot-lambda-dynamodb/.gitignore | 16 + .../aws-iot-lambda-dynamodb/.npmignore | 21 + .../aws-iot-lambda-dynamodb/README.md | 88 + .../aws-iot-lambda-dynamodb/architecture.png | Bin 0 -> 80537 bytes .../aws-iot-lambda-dynamodb/lib/index.ts | 119 + .../aws-iot-lambda-dynamodb/package.json | 83 + .../iot-lambda-dynamodb.test.js.snap | 252 ++ .../integ.iot-lambda-dynamodb.expected.json | 248 ++ .../test/integ.iot-lambda-dynamodb.ts | 41 + .../test/iot-lambda-dynamodb.test.ts | 273 +++ .../test/lambda/index.js | 10 + .../aws-iot-lambda/.eslintignore | 5 + .../aws-iot-lambda/.gitignore | 16 + .../aws-iot-lambda/.npmignore | 21 + .../aws-iot-lambda/README.md | 85 + .../aws-iot-lambda/architecture.png | Bin 0 -> 50692 bytes .../aws-iot-lambda/lib/index.ts | 111 + .../aws-iot-lambda/package.json | 79 + .../__snapshots__/iot-lambda.test.js.snap | 183 ++ .../integ.iot-lambda-new-func.expected.json | 179 ++ .../test/integ.iot-lambda-new-func.ts | 40 + ...iot-lambda-use-existing-func.expected.json | 179 ++ .../integ.iot-lambda-use-existing-func.ts | 49 + .../aws-iot-lambda/test/iot-lambda.test.ts | 319 +++ .../aws-iot-lambda/test/lambda/index.js | 10 + .../.eslintignore | 5 + .../.gitignore | 15 + .../.npmignore | 21 + .../README.md | 102 + .../architecture.png | Bin 0 -> 120293 bytes .../lib/index.ts | 128 + .../package.json | 84 + ....kinesisfirehose-analytics-s3.test.js.snap | 330 +++ .../test/integ.no-arguments.expected.json | 326 +++ .../test/integ.no-arguments.ts | 57 + .../test/lambda/index.js | 8 + .../test.kinesisfirehose-analytics-s3.test.ts | 100 + .../aws-kinesisfirehose-s3/.eslintignore | 5 + .../aws-kinesisfirehose-s3/.gitignore | 16 + .../aws-kinesisfirehose-s3/.npmignore | 21 + .../aws-kinesisfirehose-s3/README.md | 69 + .../aws-kinesisfirehose-s3/architecture.png | Bin 0 -> 115020 bytes .../aws-kinesisfirehose-s3/lib/index.ts | 147 ++ .../aws-kinesisfirehose-s3/package.json | 80 + .../test.kinesisfirehose-s3.test.js.snap | 229 ++ .../test/integ.no-arguments.expected.json | 226 ++ .../test/integ.no-arguments.ts | 26 + .../test/test.kinesisfirehose-s3.test.ts | 113 + .../aws-kinesisstreams-lambda/.eslintignore | 5 + .../aws-kinesisstreams-lambda/.gitignore | 15 + .../aws-kinesisstreams-lambda/.npmignore | 21 + .../aws-kinesisstreams-lambda/README.md | 82 + .../architecture.png | Bin 0 -> 89950 bytes .../aws-kinesisstreams-lambda/lib/index.ts | 167 ++ .../aws-kinesisstreams-lambda/package.json | 81 + .../test.kinesisstreams-lambda.test.js.snap | 325 +++ .../test/integ.deployFunction.expected.json | 322 +++ .../test/integ.deployFunction.ts | 43 + .../test/lambda/index.js | 21 + .../test/test.kinesisstreams-lambda.test.ts | 72 + .../aws-lambda-dynamodb/.eslintignore | 5 + .../aws-lambda-dynamodb/.gitignore | 16 + .../aws-lambda-dynamodb/.npmignore | 21 + .../aws-lambda-dynamodb/README.md | 78 + .../aws-lambda-dynamodb/architecture.png | Bin 0 -> 71847 bytes .../aws-lambda-dynamodb/lib/index.ts | 107 + .../aws-lambda-dynamodb/package.json | 77 + .../lambda-dynamodb.test.js.snap | 211 ++ .../integ.add-secondary-index.expected.json | 236 ++ .../test/integ.add-secondary-index.ts | 42 + .../test/integ.no-arguments.expected.json | 207 ++ .../test/integ.no-arguments.ts | 33 + .../test/integ.set-billing-mode.expected.json | 210 ++ .../test/integ.set-billing-mode.ts | 42 + .../integ.use-existing-func.expected.json | 207 ++ .../test/integ.use-existing-func.ts | 35 + .../test/lambda-dynamodb.test.ts | 307 +++ .../aws-lambda-dynamodb/test/lambda/index.js | 8 + .../.eslintignore | 5 + .../.gitignore | 16 + .../.npmignore | 21 + .../aws-lambda-elasticsearch-kibana/README.md | 84 + .../architecture.png | Bin 0 -> 96006 bytes .../lib/index.ts | 171 ++ .../package.json | 83 + .../lambda-elasticsearch-kibana.test.js.snap | 590 +++++ .../test/integ.no-arguments.expected.json | 586 +++++ .../test/integ.no-arguments.ts | 36 + .../test/lambda-elasticsearch-kibana.test.ts | 86 + .../test/lambda/index.js | 56 + .../aws-lambda-s3/.eslintignore | 5 + .../aws-lambda-s3/.gitignore | 15 + .../aws-lambda-s3/.npmignore | 21 + .../aws-lambda-s3/README.md | 76 + .../aws-lambda-s3/architecture.png | Bin 0 -> 63914 bytes .../aws-lambda-s3/lib/index.ts | 163 ++ .../aws-lambda-s3/package.json | 76 + .../test/__snapshots__/lambda-s3.test.js.snap | 2178 +++++++++++++++++ .../test/integ.deployFunction.expected.json | 272 ++ .../test/integ.deployFunction.ts | 37 + .../test/integ.existingFunction.expected.json | 272 ++ .../test/integ.existingFunction.ts | 42 + .../aws-lambda-s3/test/lambda-s3.test.ts | 210 ++ .../aws-lambda-s3/test/lambda/index.js | 21 + .../aws-lambda-sns/.eslintignore | 5 + .../aws-lambda-sns/.gitignore | 15 + .../aws-lambda-sns/.npmignore | 21 + .../aws-lambda-sns/README.md | 78 + .../aws-lambda-sns/architecture.png | Bin 0 -> 67135 bytes .../aws-lambda-sns/lib/index.ts | 126 + .../aws-lambda-sns/package.json | 78 + .../__snapshots__/lambda-sns.test.js.snap | 706 ++++++ .../test/integ.deployFunction.expected.json | 232 ++ .../test/integ.deployFunction.ts | 37 + .../test/integ.existingFunction.expected.json | 232 ++ .../test/integ.existingFunction.ts | 42 + .../aws-lambda-sns/test/lambda-sns.test.ts | 151 ++ .../aws-lambda-sns/test/lambda/index.js | 21 + .../aws-s3-lambda/.eslintignore | 5 + .../aws-s3-lambda/.gitignore | 16 + .../aws-s3-lambda/.npmignore | 21 + .../aws-s3-lambda/README.md | 85 + .../aws-s3-lambda/architecture.png | Bin 0 -> 65047 bytes .../aws-s3-lambda/lib/index.ts | 172 ++ .../aws-s3-lambda/package.json | 83 + .../test/__snapshots__/s3-lambda.test.js.snap | 434 ++++ .../integ.existing-s3-bucket.expected.json | 366 +++ .../test/integ.existing-s3-bucket.ts | 51 + .../test/integ.no-arguments.expected.json | 366 +++ .../aws-s3-lambda/test/integ.no-arguments.ts | 33 + .../aws-s3-lambda/test/lambda/index.js | 8 + .../aws-s3-lambda/test/s3-lambda.test.ts | 47 + .../aws-sns-lambda/.eslintignore | 5 + .../aws-sns-lambda/.gitignore | 16 + .../aws-sns-lambda/.npmignore | 21 + .../aws-sns-lambda/README.md | 83 + .../aws-sns-lambda/architecture.png | Bin 0 -> 65851 bytes .../aws-sns-lambda/lib/index.ts | 122 + .../aws-sns-lambda/package.json | 83 + .../__snapshots__/sns-lambda.test.js.snap | 233 ++ .../test/integ.no-arguments.expected.json | 230 ++ .../aws-sns-lambda/test/integ.no-arguments.ts | 37 + .../aws-sns-lambda/test/lambda/index.js | 8 + .../aws-sns-lambda/test/sns-lambda.test.ts | 47 + .../aws-sqs-lambda/.eslintignore | 5 + .../aws-sqs-lambda/.gitignore | 15 + .../aws-sqs-lambda/.npmignore | 21 + .../aws-sqs-lambda/README.md | 81 + .../aws-sqs-lambda/architecture.png | Bin 0 -> 97539 bytes .../aws-sqs-lambda/lib/index.ts | 146 ++ .../aws-sqs-lambda/package.json | 80 + .../test.sqs-lambda.test.js.snap | 534 ++++ .../test/integ.deployFunction.expected.json | 292 +++ .../test/integ.deployFunction.ts | 41 + .../test/integ.existingFunction.expected.json | 292 +++ .../test/integ.existingFunction.ts | 46 + .../aws-sqs-lambda/test/lambda/index.js | 21 + .../test/test.sqs-lambda.test.ts | 163 ++ .../core/.eslintignore | 7 + .../@aws-solutions-konstruk/core/.gitignore | 17 + .../@aws-solutions-konstruk/core/.npmignore | 21 + .../@aws-solutions-konstruk/core/README.md | 69 + .../@aws-solutions-konstruk/core/index.ts | 42 + .../core/lib/apigateway-defaults.ts | 69 + .../core/lib/apigateway-helper.ts | 175 ++ .../lib/cloudfront-distribution-defaults.ts | 70 + .../lib/cloudfront-distribution-helper.ts | 126 + .../core/lib/cloudwatch-log-group-defaults.ts | 27 + .../core/lib/cognito-defaults.ts | 36 + .../core/lib/cognito-helper.ts | 118 + .../core/lib/dynamodb-table-defaults.ts | 35 + .../core/lib/elasticsearch-defaults.ts | 80 + .../core/lib/elasticsearch-helper.ts | 218 ++ .../core/lib/events-rule-defaults.ts | 22 + .../core/lib/iot-topic-rule-defaults.ts | 28 + .../core/lib/kinesis-analytics-defaults.ts | 20 + .../core/lib/kinesis-analytics-helper.ts | 72 + .../core/lib/kinesis-firehose-s3-defaults.ts | 34 + .../core/lib/kinesis-streams-defaults.ts | 20 + .../core/lib/kinesis-streams-helper.ts | 54 + .../core/lib/kms-defaults.ts | 20 + .../core/lib/kms-helper.ts | 43 + .../core/lib/lambda-defaults.ts | 36 + .../lambda-event-source-mapping-defaults.ts | 50 + .../core/lib/lambda-helper.ts | 103 + .../core/lib/s3-bucket-defaults.ts | 35 + .../core/lib/s3-bucket-helper.ts | 98 + .../core/lib/sns-defaults.ts | 18 + .../core/lib/sns-helper.ts | 66 + .../core/lib/sqs-defaults.ts | 23 + .../core/lib/sqs-helper.ts | 84 + .../@aws-solutions-konstruk/core/lib/utils.ts | 74 + .../@aws-solutions-konstruk/core/package.json | 112 + .../apigateway-helper.test.js.snap | 1128 +++++++++ ...stribution-api-gateway-helper.test.js.snap | 608 +++++ ...dfront-distribution-s3-helper.test.js.snap | 289 +++ .../cloudwatch-log-group.test.js.snap | 28 + .../congnito-helper.test.js.snap | 41 + .../__snapshots__/dynamo-table.test.js.snap | 64 + .../elasticsearch-helper.test.js.snap | 326 +++ .../__snapshots__/events-rule.test.js.snap | 160 ++ .../test/__snapshots__/iot-rule.test.js.snap | 128 + .../kinesis-analytics-helper.test.js.snap | 112 + .../kinesis-analytics.test.js.snap | 14 + .../kinesis-firehose-s3-defaults.test.js.snap | 27 + .../kinesis-streams-defaults.test.js.snap | 76 + .../kinesis-streams-helper.test.js.snap | 152 ++ .../__snapshots__/kms-helper.test.js.snap | 119 + .../__snapshots__/lambda-func.test.js.snap | 142 ++ .../s3-bucket-helper.test.js.snap | 168 ++ .../test/__snapshots__/s3-bucket.test.js.snap | 33 + .../__snapshots__/sns-helper.test.js.snap | 204 ++ .../__snapshots__/sqs-helper.test.js.snap | 283 +++ .../core/test/apigateway-helper.test.ts | 217 ++ ...nt-distribution-api-gateway-helper.test.ts | 284 +++ .../cloudfront-distribution-s3-helper.test.ts | 267 ++ .../core/test/cloudwatch-log-group.test.ts | 30 + .../core/test/congnito-helper.test.ts | 198 ++ .../core/test/dynamo-table.test.ts | 158 ++ .../core/test/elasticsearch-helper.test.ts | 315 +++ .../core/test/events-rule.test.ts | 124 + .../core/test/iot-rule.test.ts | 118 + .../test/kinesis-analytics-helper.test.ts | 62 + .../core/test/kinesis-analytics.test.ts | 66 + .../test/kinesis-firehose-s3-defaults.test.ts | 62 + .../test/kinesis-streams-defaults.test.ts | 43 + .../core/test/kinesis-streams-helper.test.ts | 66 + .../core/test/kms-helper.test.ts | 60 + .../core/test/lambda-event-source.test.ts | 39 + .../core/test/lambda-func.test.ts | 232 ++ .../core/test/lambda-test/index.js | 7 + .../core/test/lambda/index.js | 10 + .../core/test/s3-bucket-helper.test.ts | 59 + .../core/test/s3-bucket.test.ts | 184 ++ .../core/test/sns-helper.test.ts | 77 + .../core/test/sqs-helper.test.ts | 74 + .../@aws-solutions-konstruk/eslintrc.yml | 49 + .../@aws-solutions-konstruk/license-header.js | 12 + .../aws-s3-static-website/.eslintignore | 6 + .../aws-s3-static-website/.gitignore | 16 + .../aws-s3-static-website/.npmignore | 21 + .../use_cases/aws-s3-static-website/README.md | 23 + .../aws-s3-static-website/architecture.png | Bin 0 -> 172868 bytes .../bin/s3-static-site-app.ts | 22 + .../use_cases/aws-s3-static-website/cdk.json | 3 + .../lib/lambda/copy_s3_objects.py | 56 + .../lib/s3-static-site-stack.ts | 77 + .../aws-s3-static-website/package.json | 52 + .../s3-static-site-stack.test.js.snap | 626 +++++ .../test/integ.basic-deployment.expected.json | 622 +++++ .../test/integ.basic-deployment.ts | 18 + .../test/s3-static-site-stack.test.ts | 119 + .../aws-s3-static-website/tsconfig.json | 31 + .../.eslintignore | 7 + .../aws-serverless-image-handler/.gitignore | 15 + .../aws-serverless-image-handler/.npmignore | 21 + .../aws-serverless-image-handler/README.md | 36 + .../architecture.png | Bin 0 -> 98366 bytes .../aws-serverless-image-handler/lib/index.ts | 243 ++ .../lib/lambda/image-handler/image-handler.js | 240 ++ .../lib/lambda/image-handler/image-request.js | 305 +++ .../lib/lambda/image-handler/index.js | 65 + .../lib/lambda/image-handler/package.json | 31 + .../image-handler/test/test-image-handler.js | 476 ++++ .../image-handler/test/test-image-request.js | 757 ++++++ .../test/test-thumbor-mapping.js | 842 +++++++ .../lambda/image-handler/thumbor-mapping.js | 256 ++ .../aws-serverless-image-handler/package.json | 87 + ...test.serverless-image-handler.test.js.snap | 910 +++++++ .../test/integ.basic-deployment.expected.json | 907 +++++++ .../test/integ.basic-deployment.ts | 34 + .../test.serverless-image-handler.test.ts | 74 + .../tsconfig.json | 34 + .../aws-serverless-web-app/.eslintignore | 7 + .../aws-serverless-web-app/.gitignore | 16 + .../aws-serverless-web-app/.npmignore | 21 + .../aws-serverless-web-app/README.md | 36 + .../aws-serverless-web-app/architecture.png | Bin 0 -> 22981 bytes .../bin/serverless-web-app.ts | 26 + .../use_cases/aws-serverless-web-app/cdk.json | 3 + .../lib/lambda/business-logic/index.js | 119 + .../lambda/cognito-config/update_s3_object.py | 46 + .../lambda/static-content/copy_s3_objects.py | 56 + .../lib/s3-static-site-stack.ts | 82 + .../lib/serverless-backend-stack.ts | 92 + .../aws-serverless-web-app/package.json | 57 + .../s3-static-site-stack.test.js.snap | 634 +++++ .../serverless-backend-stack.test.js.snap | 1096 +++++++++ .../integ.backend-deployment.expected.json | 1092 +++++++++ .../test/integ.backend-deployment.ts | 18 + ...s3-static-website-deployment.expected.json | 630 +++++ .../integ.s3-static-website-deployment.ts | 18 + .../test/s3-static-site-stack.test.ts | 113 + .../test/serverless-backend-stack.test.ts | 79 + .../aws-serverless-web-app/tsconfig.json | 31 + source/use_cases/eslintrc.yml | 49 + source/use_cases/license-header.js | 12 + 439 files changed, 59287 insertions(+), 13 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 source/lerna.json create mode 100644 source/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/__snapshots__/apigateway-dynamodb.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/apigateway-dynamodb.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/__snapshots__/test.cloudfront-apigateway.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/__snapshots__/test.cloudfront-s3.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/test/test.cloudfront-s3.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/__snapshots__/test.iot-kinesisfirehose-s3.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/test/test.iot-kinesisfirehose-s3.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/__snapshots__/iot-lambda-dynamodb.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/integ.iot-lambda-dynamodb.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/iot-lambda-dynamodb.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/__snapshots__/lambda-sns.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.deployFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/integ.existingFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda-sns.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/__snapshots__/s3-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/architecture.png create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/.eslintignore create mode 100644 source/patterns/@aws-solutions-konstruk/core/.gitignore create mode 100644 source/patterns/@aws-solutions-konstruk/core/.npmignore create mode 100644 source/patterns/@aws-solutions-konstruk/core/README.md create mode 100644 source/patterns/@aws-solutions-konstruk/core/index.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kms-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/sns-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/lib/utils.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/package.json create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/lambda/index.js create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/s3-bucket-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts create mode 100644 source/patterns/@aws-solutions-konstruk/eslintrc.yml create mode 100644 source/patterns/@aws-solutions-konstruk/license-header.js create mode 100644 source/use_cases/aws-s3-static-website/.eslintignore create mode 100644 source/use_cases/aws-s3-static-website/.gitignore create mode 100644 source/use_cases/aws-s3-static-website/.npmignore create mode 100644 source/use_cases/aws-s3-static-website/README.md create mode 100644 source/use_cases/aws-s3-static-website/architecture.png create mode 100644 source/use_cases/aws-s3-static-website/bin/s3-static-site-app.ts create mode 100644 source/use_cases/aws-s3-static-website/cdk.json create mode 100644 source/use_cases/aws-s3-static-website/lib/lambda/copy_s3_objects.py create mode 100644 source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts create mode 100644 source/use_cases/aws-s3-static-website/package.json create mode 100644 source/use_cases/aws-s3-static-website/test/__snapshots__/s3-static-site-stack.test.js.snap create mode 100644 source/use_cases/aws-s3-static-website/test/integ.basic-deployment.expected.json create mode 100644 source/use_cases/aws-s3-static-website/test/integ.basic-deployment.ts create mode 100644 source/use_cases/aws-s3-static-website/test/s3-static-site-stack.test.ts create mode 100644 source/use_cases/aws-s3-static-website/tsconfig.json create mode 100644 source/use_cases/aws-serverless-image-handler/.eslintignore create mode 100644 source/use_cases/aws-serverless-image-handler/.gitignore create mode 100644 source/use_cases/aws-serverless-image-handler/.npmignore create mode 100644 source/use_cases/aws-serverless-image-handler/README.md create mode 100644 source/use_cases/aws-serverless-image-handler/architecture.png create mode 100644 source/use_cases/aws-serverless-image-handler/lib/index.ts create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-handler.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-request.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/index.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/package.json create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-handler.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-request.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-thumbor-mapping.js create mode 100755 source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/thumbor-mapping.js create mode 100644 source/use_cases/aws-serverless-image-handler/package.json create mode 100644 source/use_cases/aws-serverless-image-handler/test/__snapshots__/test.serverless-image-handler.test.js.snap create mode 100644 source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.expected.json create mode 100644 source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.ts create mode 100644 source/use_cases/aws-serverless-image-handler/test/test.serverless-image-handler.test.ts create mode 100644 source/use_cases/aws-serverless-image-handler/tsconfig.json create mode 100644 source/use_cases/aws-serverless-web-app/.eslintignore create mode 100644 source/use_cases/aws-serverless-web-app/.gitignore create mode 100644 source/use_cases/aws-serverless-web-app/.npmignore create mode 100644 source/use_cases/aws-serverless-web-app/README.md create mode 100644 source/use_cases/aws-serverless-web-app/architecture.png create mode 100644 source/use_cases/aws-serverless-web-app/bin/serverless-web-app.ts create mode 100644 source/use_cases/aws-serverless-web-app/cdk.json create mode 100644 source/use_cases/aws-serverless-web-app/lib/lambda/business-logic/index.js create mode 100644 source/use_cases/aws-serverless-web-app/lib/lambda/cognito-config/update_s3_object.py create mode 100644 source/use_cases/aws-serverless-web-app/lib/lambda/static-content/copy_s3_objects.py create mode 100644 source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts create mode 100644 source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts create mode 100644 source/use_cases/aws-serverless-web-app/package.json create mode 100644 source/use_cases/aws-serverless-web-app/test/__snapshots__/s3-static-site-stack.test.js.snap create mode 100644 source/use_cases/aws-serverless-web-app/test/__snapshots__/serverless-backend-stack.test.js.snap create mode 100644 source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.expected.json create mode 100644 source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.ts create mode 100644 source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.expected.json create mode 100644 source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.ts create mode 100644 source/use_cases/aws-serverless-web-app/test/s3-static-site-stack.test.ts create mode 100644 source/use_cases/aws-serverless-web-app/test/serverless-backend-stack.test.ts create mode 100644 source/use_cases/aws-serverless-web-app/tsconfig.json create mode 100644 source/use_cases/eslintrc.yml create mode 100644 source/use_cases/license-header.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..9e42524d5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Change Log +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). + +## [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 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b627cfa6..3b6446687 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ ## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 914e0741d..79071822b 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, or recently closed, issues to make sure somebody else hasn't already +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 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 @@ -41,7 +41,7 @@ GitHub provides additional document on [forking a repository](https://help.githu ## 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' issues is a great place to start. +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. ## Code of Conduct @@ -56,6 +56,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](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-konstruk/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 b/NOTICE index 616fc5889..135629617 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1,47 @@ -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +AWS Konstruk +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/ +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. + +********************** +THIRD PARTY COMPONENTS +********************** +This software includes third party software subject to the following copyrights: + +@types/jest under the Massachusetts Institute of Technology (MIT) license +@types/node under the Massachusetts Institute of Technology (MIT) license +@typescript-eslint/eslint-plugin under the Massachusetts Institute of Technology (MIT) license +@typescript-eslint/parser under the BSD-2-Clause license +aws-cdk under the Apache License Version 2.0 +aws-sdk under the Apache License Version 2.0 +aws-sdk-mock under the Apache License Version 2.0 +bootstrap under the Massachusetts Institute of Technology (MIT) license +chai under the Massachusetts Institute of Technology (MIT) license +color under the Massachusetts Institute of Technology (MIT) license +color-name under the Massachusetts Institute of Technology (MIT) license +deepmerge under the MIT License +eslint under the Massachusetts Institute of Technology (MIT) license +eslint-import-resolver-node under the Massachusetts Institute of Technology (MIT) license +eslint-import-resolver-typescript under the ISC license +eslint-plugin-import under the Massachusetts Institute of Technology (MIT) license +eslint-plugin-license-header under the Massachusetts Institute of Technology (MIT) license +fs-extra under the Massachusetts Institute of Technology (MIT) license +jest under the Massachusetts Institute of Technology (MIT) license +jsii under the Apache License Version 2.0 +jsii-pacmak under the Apache License Version 2.0 +lerna under the Massachusetts Institute of Technology (MIT) license +minimist under the Massachusetts Institute of Technology (MIT) license +mocha under the Massachusetts Institute of Technology (MIT) license +moment under the Massachusetts Institute of Technology (MIT) license +npm-run-all under the Massachusetts Institute of Technology (MIT) license +nyc under the ISC license +sharp under the Apache License Version 2.0 +sinon under the BSD-3-Clause license +sinon-chai under the BSD-2-Clause license +source-map-support under the Massachusetts Institute of Technology (MIT) license +tslint under the Apache License Version 2.0 +typescript under the Apache License Version 2.0 +uuid under the Massachusetts Institute of Technology (MIT) license \ No newline at end of file diff --git a/README.md b/README.md index 8f46b7c6c..e0bce0513 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,60 @@ -## My Project +# API Reference + -TODO: Fill this README out! +--- -Be sure to: +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) -* Change the title in this README -* Edit your repository description on GitHub +> **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. -## License +--- + -This project is licensed under the Apache-2.0 License. +| **API Reference**:| http://docs.awssolutionsbuilder.com/aws-solutions-konstruk/latest/| +|:-------------|:-------------| +
+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 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 CDK is available in the following languages: + +* JavaScript, TypeScript (Node.js ≥ 10.3.0) +* Python (Python ≥ 3.6) + +## Modules + +The Konstruk 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. + +## Module Contents + +Modules contain the following types: + +* __Patterns__ - All higher-level, multi-services constructs in this library. +* __Other Types__ - All non-construct classes, interfaces, structs and enums that exist to support the patterns. + +Patterns take a set of (input) properties in their constructor; the set of properties (and which ones are required) can be seen on a pattern's documentation page. + +The pattern's documentation page also lists the available methods to call and the properties which can be used to retrieve information about the pattern after it has been instantiated. + +## 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: + +* __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 +* __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 +* __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 + +*** +© 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 new file mode 100644 index 000000000..80d43a175 --- /dev/null +++ b/source/lerna.json @@ -0,0 +1,11 @@ +{ + "lerna": "3.15.0", + "npmClient": "yarn", + "useWorkspaces": true, + "packages": [ + "./patterns/@aws-solutions-konstruk/*", + "./use_cases/*" + ], + "rejectCycles": "true", + "version": "0.8.0" +} diff --git a/source/package.json b/source/package.json new file mode 100644 index 000000000..8523bf35e --- /dev/null +++ b/source/package.json @@ -0,0 +1,44 @@ +{ + "name": "aws-solutions-konstruk", + "version": "0.8.0", + "description": "AWS Solutions Konstruk Library", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-konstruk.git", + "directory": "source" + }, + "license": "Apache-2.0", + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com" + }, + "private": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^2.14.0", + "@typescript-eslint/parser": "^2.14.0", + "eslint": "^6.8.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-import-resolver-typescript": "^2.0.0", + "eslint-plugin-import": "^2.19.1", + "eslint-plugin-license-header": "^0.2.0", + "fs-extra": "^8.1.0", + "jest": "^24.9.0", + "jsii": "~0.22.0", + "jsii-pacmak": "~0.22.0", + "tslint": "^5.20.1", + "typescript": "~3.8.2" + }, + "devDependencies": { + "lerna": "^3.18.4" + }, + "workspaces": { + "packages": [ + "./patterns/@aws-solutions-konstruk/*", + "./use_cases/*" + ], + "nohoist": [ + "**/deepmerge", + "**/deepmerge/**" + ] + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.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-apigateway-dynamodb/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.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-apigateway-dynamodb/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/.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-apigateway-dynamodb/README.md b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md new file mode 100644 index 000000000..ce68175ce --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/README.md @@ -0,0 +1,77 @@ +# aws-apigateway-dynamodb 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/aws-apigateway-dynamodb/| +|:-------------|:-------------| +
+ + +| **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`| + +## Overview +This AWS Solutions Konstruk 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"; + +const props: ApiGatewayToDynamoDBProps = {}; + +new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb-default', props); + +``` + +## Initializer + +``` text +new ApiGatewayToDynamoDB(scope: Construct, id: string, props: ApiGatewayToDynamoDBProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`ApiGatewayToDynamoDBProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|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.| +|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.| +|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.| + +## 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)|Retruns an instance of dynamodb.Table created by the construct.| + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..4397dc3dcf2e6ad238da75e7d50361f6345cae1a GIT binary patch literal 71973 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�>E4lXA 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 { + const stack = new Stack(); + const apiGatewayToDynamoDBProps: ApiGatewayToDynamoDBProps = {}; + new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb-default', apiGatewayToDynamoDBProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check getter methods', () => { + 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(); +}); + +test('check allow CRUD operations', () => { + const stack = new Stack(); + const apiGatewayToDynamoDBProps: ApiGatewayToDynamoDBProps = { + allowReadOperation: true, + allowCreateOperation: true, + createRequestTemplate: "{}", + allowDeleteOperation: true, + allowUpdateOperation: true, + updateRequestTemplate: "{}" + }; + new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb', apiGatewayToDynamoDBProps); + + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: "dynamodb:PutItem", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + Action: "dynamodb:Query", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + Action: "dynamodb:UpdateItem", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + Action: "dynamodb:DeleteItem", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + } + ], + Version: "2012-10-17" + }, + PolicyName: "testapigatewaydynamodbapigatewayroleDefaultPolicy43AC565D", + Roles: [ + { + Ref: "testapigatewaydynamodbapigatewayrole961B19C4" + } + ] + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "GET", + AuthorizationType: "AWS_IAM" + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "POST", + AuthorizationType: "AWS_IAM" + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "PUT", + AuthorizationType: "AWS_IAM" + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "DELETE", + AuthorizationType: "AWS_IAM" + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Resource", { + PathPart: "{id}", + }); +}); + +test('check allow read and update only', () => { + const stack = new Stack(); + const apiGatewayToDynamoDBProps: ApiGatewayToDynamoDBProps = { + allowUpdateOperation: true, + updateRequestTemplate: "{}" + }; + new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb', apiGatewayToDynamoDBProps); + + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: "dynamodb:Query", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + Action: "dynamodb:UpdateItem", + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + } + ], + Version: "2012-10-17" + }, + PolicyName: "testapigatewaydynamodbapigatewayroleDefaultPolicy43AC565D", + Roles: [ + { + Ref: "testapigatewaydynamodbapigatewayrole961B19C4" + } + ] + }); + + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "GET", + AuthorizationType: "AWS_IAM" + }); +}); + +test('check using custom partition key for dynamodb', () => { + const stack = new Stack(); + const apiGatewayToDynamoDBProps: ApiGatewayToDynamoDBProps = { + dynamoTableProps: { + partitionKey: { + name: 'page_id', + type: AttributeType.STRING + } + } + }; + new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb', apiGatewayToDynamoDBProps); + + expect(stack).toHaveResource("AWS::ApiGateway::Resource", { + PathPart: "{page_id}", + }); + +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json new file mode 100644 index 000000000..ac65b24ea --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.expected.json @@ -0,0 +1,635 @@ +{ + "Description": "Integration Test for aws-apigateway-dynamodb", + "Resources": { + "testapigatewaydynamodbDynamoTableEEE3F463": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaydynamodbRestApi80489300": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "testapigatewaydynamodbRestApiDeployment1898674B2ca5fe4ea0afc42b42d1b0c68ddb65ce": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testapigatewaydynamodbRestApiidDELETE6FC8A9F3", + "testapigatewaydynamodbRestApiidGET6196F638", + "testapigatewaydynamodbRestApiidPUT1F965B23", + "testapigatewaydynamodbRestApiid78018D34", + "testapigatewaydynamodbRestApiPOSTE99BD0BD" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testapigatewaydynamodbRestApiDeploymentStageprod2855C2C3": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaydynamodbApiAccessLogGroup3F457756", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testapigatewaydynamodbRestApiDeployment1898674B2ca5fe4ea0afc42b42d1b0c68ddb65ce" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testapigatewaydynamodbRestApiid78018D34": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testapigatewaydynamodbRestApi80489300", + "RootResourceId" + ] + }, + "PathPart": "{id}", + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + } + } + }, + "testapigatewaydynamodbRestApiidGET6196F638": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Ref": "testapigatewaydynamodbRestApiid78018D34" + }, + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaydynamodbapigatewayrole961B19C4", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/json'" + }, + "RequestTemplates": { + "application/json": { + "Fn::Join": [ + "", + [ + "{\r\n\"TableName\": \"", + { + "Ref": "testapigatewaydynamodbDynamoTableEEE3F463" + }, + "\",\r\n \"KeyConditionExpression\": \"id = :v1\",\r\n \"ExpressionAttributeValues\": {\r\n \":v1\": {\r\n \"S\": \"$input.params('id')\"\r\n }\r\n }\r\n}" + ] + ] + } + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":dynamodb:action/Query" + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaydynamodbRestApiidPUT1F965B23": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "PUT", + "ResourceId": { + "Ref": "testapigatewaydynamodbRestApiid78018D34" + }, + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaydynamodbapigatewayrole961B19C4", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/json'" + }, + "RequestTemplates": { + "application/json": { + "Fn::Join": [ + "", + [ + "{\r\n \"TableName\": \"", + { + "Ref": "testapigatewaydynamodbDynamoTableEEE3F463" + }, + "\",\r\n \"Key\": {\r\n \"id\": {\r\n \"S\": \"$input.path('$.id')\"\r\n }\r\n },\r\n \"ExpressionAttributeValues\": {\r\n \":event_count\": {\r\n \"N\": \"$input.path('$.EventCount')\"\r\n },\r\n \":message\": {\r\n \"S\": \"$input.path('$.Message')\"\r\n }\r\n },\r\n \"UpdateExpression\": \"ADD EventCount :event_count SET Message = :message\",\r\n \"ReturnValues\": \"ALL_NEW\"\r\n}" + ] + ] + } + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":dynamodb:action/UpdateItem" + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaydynamodbRestApiidDELETE6FC8A9F3": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "DELETE", + "ResourceId": { + "Ref": "testapigatewaydynamodbRestApiid78018D34" + }, + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaydynamodbapigatewayrole961B19C4", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/json'" + }, + "RequestTemplates": { + "application/json": { + "Fn::Join": [ + "", + [ + "{\r\n \"TableName\": \"", + { + "Ref": "testapigatewaydynamodbDynamoTableEEE3F463" + }, + "\",\r\n \"Key\": {\r\n \"id\": {\r\n \"S\": \"$input.params('id')\"\r\n }\r\n },\r\n \"ConditionExpression\": \"attribute_not_exists(Replies)\",\r\n \"ReturnValues\": \"ALL_OLD\"\r\n}" + ] + ] + } + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":dynamodb:action/DeleteItem" + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaydynamodbRestApiPOSTE99BD0BD": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "POST", + "ResourceId": { + "Fn::GetAtt": [ + "testapigatewaydynamodbRestApi80489300", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaydynamodbapigatewayrole961B19C4", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/json'" + }, + "RequestTemplates": { + "application/json": { + "Fn::Join": [ + "", + [ + "{\r\n \"TableName\": \"", + { + "Ref": "testapigatewaydynamodbDynamoTableEEE3F463" + }, + "\",\r\n \"Item\": {\r\n \"id\": {\r\n \"S\": \"$input.path('$.id')\"\r\n },\r\n \"EventCount\": {\r\n \"N\": \"$input.path('$.EventCount')\"\r\n },\r\n \"Message\": {\r\n \"S\": \"$input.path('$.Message')\"\r\n }\r\n }\r\n}" + ] + ] + } + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":dynamodb:action/PutItem" + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaydynamodbRestApiUsagePlan244F06C8": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + "Stage": { + "Ref": "testapigatewaydynamodbRestApiDeploymentStageprod2855C2C3" + }, + "Throttle": {} + } + ] + } + }, + "testapigatewaydynamodbApiAccessLogGroup3F457756": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaydynamodbLambdaRestApiCloudWatchRoleD176CA9E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testapigatewaydynamodbLambdaRestApiAccount3608999D": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testapigatewaydynamodbLambdaRestApiCloudWatchRoleD176CA9E", + "Arn" + ] + } + }, + "DependsOn": [ + "testapigatewaydynamodbRestApi80489300" + ] + }, + "testapigatewaydynamodbapigatewayrole961B19C4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testapigatewaydynamodbapigatewayroleDefaultPolicy43AC565D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "dynamodb:PutItem", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + "Action": "dynamodb:Query", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + "Action": "dynamodb:UpdateItem", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + }, + { + "Action": "dynamodb:DeleteItem", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testapigatewaydynamodbDynamoTableEEE3F463", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testapigatewaydynamodbapigatewayroleDefaultPolicy43AC565D", + "Roles": [ + { + "Ref": "testapigatewaydynamodbapigatewayrole961B19C4" + } + ] + } + } + }, + "Outputs": { + "testapigatewaydynamodbRestApiEndpoint18D89CA6": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testapigatewaydynamodbRestApi80489300" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testapigatewaydynamodbRestApiDeploymentStageprod2855C2C3" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.ts new file mode 100644 index 000000000..15442846c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.apigateway-dynamodb-CRUD.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. + */ + +// Imports +import { App, Stack } from "@aws-cdk/core"; +import { ApiGatewayToDynamoDBProps, ApiGatewayToDynamoDB } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-dynamodb'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-dynamodb'; + +// Definitions +const props: ApiGatewayToDynamoDBProps = { + allowReadOperation: true, + allowCreateOperation: true, + allowDeleteOperation: true, + allowUpdateOperation: true, + createRequestTemplate: "{\r\n \"TableName\": \"${Table}\",\r\n \"Item\": {\r\n \"id\": {\r\n \"S\": \"$input.path('$.id')\"\r\n },\r\n \"EventCount\": {\r\n \"N\": \"$input.path('$.EventCount')\"\r\n },\r\n \"Message\": {\r\n \"S\": \"$input.path('$.Message')\"\r\n }\r\n }\r\n}", + updateRequestTemplate: "{\r\n \"TableName\": \"${Table}\",\r\n \"Key\": {\r\n \"id\": {\r\n \"S\": \"$input.path('$.id')\"\r\n }\r\n },\r\n \"ExpressionAttributeValues\": {\r\n \":event_count\": {\r\n \"N\": \"$input.path('$.EventCount')\"\r\n },\r\n \":message\": {\r\n \"S\": \"$input.path('$.Message')\"\r\n }\r\n },\r\n \"UpdateExpression\": \"ADD EventCount :event_count SET Message = :message\",\r\n \"ReturnValues\": \"ALL_NEW\"\r\n}" +}; + +new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..258a1c11d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.expected.json @@ -0,0 +1,350 @@ +{ + "Description": "Integration Test for aws-apigateway-dynamodb", + "Resources": { + "testapigatewaydynamodbdefaultDynamoTable0720D92C": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaydynamodbdefaultRestApi9102FDF9": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "testapigatewaydynamodbdefaultRestApiDeploymentFAC726F377bb1a1fb193e128da423ded28aa899d": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testapigatewaydynamodbdefaultRestApiidGET94B6F433", + "testapigatewaydynamodbdefaultRestApiidFD6A9E91" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testapigatewaydynamodbdefaultRestApiDeploymentStageprod7834D304": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaydynamodbdefaultApiAccessLogGroup0192183A", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testapigatewaydynamodbdefaultRestApiDeploymentFAC726F377bb1a1fb193e128da423ded28aa899d" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testapigatewaydynamodbdefaultRestApiidFD6A9E91": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testapigatewaydynamodbdefaultRestApi9102FDF9", + "RootResourceId" + ] + }, + "PathPart": "{id}", + "RestApiId": { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + } + } + }, + "testapigatewaydynamodbdefaultRestApiidGET94B6F433": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Ref": "testapigatewaydynamodbdefaultRestApiidFD6A9E91" + }, + "RestApiId": { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaydynamodbdefaultapigatewayrole0CDF008A", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/json'" + }, + "RequestTemplates": { + "application/json": { + "Fn::Join": [ + "", + [ + "{\r\n\"TableName\": \"", + { + "Ref": "testapigatewaydynamodbdefaultDynamoTable0720D92C" + }, + "\",\r\n \"KeyConditionExpression\": \"id = :v1\",\r\n \"ExpressionAttributeValues\": {\r\n \":v1\": {\r\n \"S\": \"$input.params('id')\"\r\n }\r\n }\r\n}" + ] + ] + } + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":dynamodb:action/Query" + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaydynamodbdefaultRestApiUsagePlanA266BB3D": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + }, + "Stage": { + "Ref": "testapigatewaydynamodbdefaultRestApiDeploymentStageprod7834D304" + }, + "Throttle": {} + } + ] + } + }, + "testapigatewaydynamodbdefaultApiAccessLogGroup0192183A": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaydynamodbdefaultLambdaRestApiCloudWatchRoleEF1FBFD7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testapigatewaydynamodbdefaultLambdaRestApiAccountE6585EBB": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testapigatewaydynamodbdefaultLambdaRestApiCloudWatchRoleEF1FBFD7", + "Arn" + ] + } + }, + "DependsOn": [ + "testapigatewaydynamodbdefaultRestApi9102FDF9" + ] + }, + "testapigatewaydynamodbdefaultapigatewayrole0CDF008A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testapigatewaydynamodbdefaultapigatewayroleDefaultPolicyE0B5E59D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "dynamodb:Query", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testapigatewaydynamodbdefaultDynamoTable0720D92C", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testapigatewaydynamodbdefaultapigatewayroleDefaultPolicyE0B5E59D", + "Roles": [ + { + "Ref": "testapigatewaydynamodbdefaultapigatewayrole0CDF008A" + } + ] + } + } + }, + "Outputs": { + "testapigatewaydynamodbdefaultRestApiEndpointD5AD8DB9": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testapigatewaydynamodbdefaultRestApi9102FDF9" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testapigatewaydynamodbdefaultRestApiDeploymentStageprod7834D304" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts new file mode 100644 index 000000000..db6b9c799 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-dynamodb/test/integ.no-arguments.ts @@ -0,0 +1,30 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { ApiGatewayToDynamoDBProps, ApiGatewayToDynamoDB } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-dynamodb-default'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-dynamodb'; + +// Definitions +const props: ApiGatewayToDynamoDBProps = { +}; + +new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb-default', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-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-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-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-apigateway-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md new file mode 100644 index 000000000..3de5d141c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/README.md @@ -0,0 +1,78 @@ +# aws-apigateway-lambda 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/aws-apigateway-lambda/| +|:-------------|:-------------| +
+ +| **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`| + +## Overview + +This AWS Solutions Konstruk 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'); + +new ApiGatewayToLambda(stack, 'ApiGatewayToLambdaPattern', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}); + +``` + +## Initializer + +``` text +new ApiGatewayToLambda(scope: Construct, id: string, props: ApiGatewayToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`ApiGatewayToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function. If set to false, you must provide an existing function for the `existingLambdaObj` property.| +|existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|An optional, existing Lambda function. This property is required if `deployLambda` is set to false.| +|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.| +|apiGatewayProps?|[`api.LambdaRestApiProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html)|Optional user-provided props to override the default props for the API.| + +## 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.| +|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.| + +## 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-konstruk/aws-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..4e617ab6e85f83487287e37733f9ce483e5d6723 GIT binary patch literal 98366 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�_LfTmp zb3a{m|4qzT(J96`?EJdc)W`Aef17nHIronkLpRl4(+zt69A=zhA?!#8c9H6N04`60 zcJ(iIKQtNS*NFc3QB>Xe;Js=v`Vq9PYX|-}p&@HaWnZ)Z3*FBTE&`3nIsvziQH74n zOL_ixJ30YhbOh|;u^dUD7Pm3ZBm9`%=le3%c)!e0aAh6=iFa~z6TbFXQ58CNf z+K)}s{5v<;ifREd?D5iJBzssP&344qUCg;QVC5QnTr)Hl!o(=}BjNY32gtl=FEY8exxzOpc)mlfD&3*c0hACOOM%w)lpBRY`Piy@_90HaSu;n zJ<@NW163bqyBJQ@SQGMuu({#=%Lh{`IZ=^0R=c~$O0Xo&a8o?xo|t@Qs*|Rh$2)Fu zJdK4)@!fSJ_3j3|*OzFH%R31hH&b%EYBC#krc0!?=(uo@cw#$KFXlZkWib$m3EMw) z+BV+=a{MvtfrCrToS762d&mlTY}2sP%jxs*7R>V=0Ns)@yyRfI%kMv!C!GC(XR50N9wa1fqPCzG`A4qH+Xa0K58@O{nVk zuH&VPp#!t8IP@{__6*~Z9ilp&=RL$#ZdwV_cZnr7{&3~EgI)pAmUe5Vt zdRY2suJIO-Z3FP{=RGvotfFF8<&@sh#2Q@sjQ)}tCyKp6QGs7sLOFK1&kc*YH62XY z^xHJeE7xXN>J^uFi{zOM>e#{kwrp5wUyLz{lt%~yPv zeyyexVdCK-qCXWr4o9Q$9Q{9brXbe9j+a}Bp!Bj2} znXB$TPaqJ>@^2X&t3?T3!Ohfd9Nr2ZwM`fOEfnT+V<6|dwOUI5Iuxd}>m$ZcSg_|& z-{|bLWcn{uc|TmbQ4Lf<3ytiJjoiO7!dtWn_wI60a@YH@0~(H+&L<`63xpg2>r=8@4p z$H!Fef}gs3o7h@hra?=-DYQ8Ctk{JoLANyc&i1ziBY}Ui)WkYEgQ6lcbYx4Cey|kn zo8o>ZSxvJF+`G=zc1jqQEqBn;9Tg*%09N#OlD8S#&u=E^w(pmHINsQK7^G^A+n=k2 z-83VL^o2~e(YND)F^A6U`JiiiO4`lOwoJs(N@K4q8Ppe44Bd&nGBtcz5~@4Ll{;Hq3Bbt?M%ida7i4e3$pV58hg6#)_>H|MJy zOUV;+(o+SzVT^#q`Jt1hK%2ddZfyAVJK5HZNu9ryJan5MbLVPJHZ@xRf|Aq(5kctR zz?aYJ8fK6rQ(V z$=_&H^|G>ohkX9z01cRwR_)rN0$Z)%<&qhVDB5|NZ}n1?URjQ}zAxZulQYoJVKqFKtb| KzsfXh-~2C~^N*$g literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts new file mode 100644 index 000000000..34b852ae3 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/lib/index.ts @@ -0,0 +1,122 @@ +/** + * 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 * as api from '@aws-cdk/aws-apigateway'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-konstruk/core'; +import { Construct } from '@aws-cdk/core'; +import { LogGroup } from '@aws-cdk/aws-logs'; + +/** + * The properties for the ApiGatewayToLambda class. + */ +export interface ApiGatewayToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide an existing function for the `existingLambdaObj` property. + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * An optional, existing Lambda function. + * This property is required if `deployLambda` is set to false. + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user-provided props to override the default props for the Lambda function. + * This property is only required if `deployLambda` is set to true. + * + * @default - Default props are used. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any + /** + * Optional user-provided props to override the default props for the API. + * + * @default - Default props are used. + */ + readonly apiGatewayProps?: api.LambdaRestApiProps | any +} + +/** + * @summary The ApiGatewayToLambda class. + */ +export class ApiGatewayToLambda extends Construct { + // Private variables + private api: api.RestApi; + private fn: lambda.Function; + + /** + * @summary Constructs a new instance of the ApiGatewayToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CloudFrontToApiGatewayToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: ApiGatewayToLambdaProps) { + super(scope, id); + + // Setup the Lambda function + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Setup the API Gateway + this.api = defaults.GlobalLambdaRestApi(scope, this.fn, props.apiGatewayProps); + + // Setup the log group + const logGroup = new LogGroup(this, 'ApiAccessLogGroup'); + + // Configure API Gateway Access logging + const stage: api.CfnStage = this.api.deploymentStage.node.findChild('Resource') as api.CfnStage; + stage.accessLogSetting = { + destinationArn: logGroup.logGroupArn, + format: "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }; + const deployment: api.CfnDeployment = this.api.latestDeployment?.node.findChild('Resource') as api.CfnDeployment; + deployment.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W45', + reason: `ApiGateway does have access logging configured as part of AWS::ApiGateway::Stage.` + }] + } + }; + } + + /** + * @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; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json new file mode 100644 index 000000000..10e5292a6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/package.json @@ -0,0 +1,79 @@ +{ + "name": "@aws-solutions-konstruk/aws-apigateway-lambda", + "version": "0.8.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" + }, + "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.apigatewaylambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "apigatewaylambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.ApiGatewayLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap new file mode 100644 index 000000000..23318957b --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/__snapshots__/test.apigateway-lambda.test.js.snap @@ -0,0 +1,1136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pattern deployment with existing Lambda function 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "ExistingLambdaFunctionF606C520": Object { + "DependsOn": Array [ + "ExistingLambdaFunctionServiceRole7CC6DE65", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionServiceRole7CC6DE65", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "ExistingLambdaFunctionServiceRole7CC6DE65": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionRestApiANY3A99B4EE": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestRestApiANY79BD91F2": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiDeployment180EC503e06cd95e76bb0f117b881703f487bbf5": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway does have access logging configured as part of AWS::ApiGateway::Stage.", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "testapigatewaylambdaApiAccessLogGroupEB3253A2", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC503e06cd95e76bb0f117b881703f487bbf5", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionRestApiANYproxy9C9912F9": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestRestApiANYproxyCB7BC56D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionF606C520", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "testapigatewaylambdaApiAccessLogGroupEB3253A2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RetentionInDays": 731, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; + +exports[`Pattern deployment with new Lambda function 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionRestApiANY3A99B4EE": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestRestApiANY79BD91F2": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway does have access logging configured as part of AWS::ApiGateway::Stage.", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "testapigatewaylambdaApiAccessLogGroupEB3253A2", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionRestApiANYproxy9C9912F9": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestRestApiANYproxyCB7BC56D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "testapigatewaylambdaApiAccessLogGroupEB3253A2": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "RetentionInDays": 731, + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json new file mode 100644 index 000000000..90ccde175 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.expected.json @@ -0,0 +1,583 @@ +{ + "Description": "Integration Test for aws-apigateway-lambda", + "Resources": { + "testapigatewaylambdaApiAccessLogGroupEB3253A2": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.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." + } + ] + } + } + }, + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway does have access logging configured as part of AWS::ApiGateway::Stage." + } + ] + } + } + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaylambdaApiAccessLogGroupEB3253A2", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "RestApiproxyC95856DD": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "RestApi0C43BF4B" + } + } + }, + "RestApiproxyANYApiPermissiontestapigatewaylambdaRestApi54300087ANYproxy2DB4C4FC": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANYApiPermissionTesttestapigatewaylambdaRestApi54300087ANYproxy9A552081": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANY1786B242": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "RestApiproxyC95856DD" + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiANYApiPermissiontestapigatewaylambdaRestApi54300087ANY87811EBD": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/" + ] + ] + } + } + }, + "RestApiANYApiPermissionTesttestapigatewaylambdaRestApi54300087ANY855753FC": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "RestApiANYA7C1DC94": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiUsagePlan6E1C537A": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Stage": { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "Throttle": {} + } + ] + } + }, + "ApiAccessLogGroupCEA70788": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LambdaRestApiCloudWatchRoleF339D4E6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "LambdaRestApiAccount": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts new file mode 100644 index 000000000..5248dd78e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.deployFunction.ts @@ -0,0 +1,37 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { ApiGatewayToLambda, ApiGatewayToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-lambda'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-lambda'; + +// Definitions +const props: ApiGatewayToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}; + +new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json new file mode 100644 index 000000000..8cd444c91 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.expected.json @@ -0,0 +1,583 @@ +{ + "Description": "Integration Test for aws-apigateway-lambda", + "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": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.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." + } + ] + } + } + }, + "testapigatewaylambdaApiAccessLogGroupEB3253A2": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway does have access logging configured as part of AWS::ApiGateway::Stage." + } + ] + } + } + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaylambdaApiAccessLogGroupEB3253A2", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "RestApiproxyC95856DD": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "RestApi0C43BF4B" + } + } + }, + "RestApiproxyANYApiPermissiontestapigatewaylambdaRestApi54300087ANYproxy2DB4C4FC": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANYApiPermissionTesttestapigatewaylambdaRestApi54300087ANYproxy9A552081": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANY1786B242": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "RestApiproxyC95856DD" + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiANYApiPermissiontestapigatewaylambdaRestApi54300087ANY87811EBD": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/" + ] + ] + } + } + }, + "RestApiANYApiPermissionTesttestapigatewaylambdaRestApi54300087ANY855753FC": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "RestApiANYA7C1DC94": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiUsagePlan6E1C537A": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Stage": { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "Throttle": {} + } + ] + } + }, + "ApiAccessLogGroupCEA70788": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LambdaRestApiCloudWatchRoleF339D4E6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "LambdaRestApiAccount": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts new file mode 100644 index 000000000..5d311aa88 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/integ.existingFunction.ts @@ -0,0 +1,44 @@ +/** + * 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 { 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'; + +// App setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-lambda'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-lambda'; + +// Lambda function setup +const lambdaFunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) +}; + +const func = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + +// Api gateway setup +const props: ApiGatewayToLambdaProps = { + deployLambda: false, + existingLambdaObj: func +}; + +// Instantiate construct +new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js new file mode 100644 index 000000000..51fdc6953 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/lambda/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +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/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts new file mode 100644 index 000000000..c2e23ae31 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-lambda/test/test.apigateway-lambda.test.ts @@ -0,0 +1,138 @@ +/** + * 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 { ApiGatewayToLambda, ApiGatewayToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Pattern deployment with new Lambda function +// -------------------------------------------------------------- +test('Pattern deployment with new Lambda function', () => { + // Initial Setup + const stack = new Stack(); + const props: ApiGatewayToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } + }; + new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Pattern deployment with existing Lambda function +// -------------------------------------------------------------- +test('Pattern deployment with existing Lambda function', () => { + // Initial Setup + const stack = new Stack(); + const fn = new lambda.Function(stack, 'ExistingLambdaFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }); + const props: ApiGatewayToLambdaProps = { + deployLambda: false, + existingLambdaObj: fn + }; + new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test for error with deployLambda=false and +// existingLambdaObj=undefined (not supplied by user). +// -------------------------------------------------------------- +test('Error on deployLambda=false and existingLambdaObj=undefined', () => { + // Initial Setup + const stack = new Stack(); + const props: ApiGatewayToLambdaProps = { + deployLambda: false + }; + const app = () => { + new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + }; + // Assertion 1 + expect(app).toThrowError(); +}); + +// -------------------------------------------------------------- +// Test deployLambda=true with lambdaFunctionProps. +// -------------------------------------------------------------- +test('Test deployLambda=true with lambdaFunctionProps', () => { + // Initial Setup + const stack = new Stack(); + const props: ApiGatewayToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`), + environment: { + OVERRIDE_STATUS: 'true' + } + }, + apiGatewayProps: { + description: "sampleApiProp" + } + }; + const app = new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + // Assertion 1 + expect(app.lambdaFunction()).toHaveProperty('environment.OVERRIDE_STATUS', 'true'); +}); + +// -------------------------------------------------------------- +// Test getter methods +// -------------------------------------------------------------- +test('Test getter methods', () => { + // Initial Setup + const stack = new Stack(); + const props: ApiGatewayToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } + }; + const app = new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + // Assertion 1 + expect(app.lambdaFunction()).toBeDefined(); + // Assertion 2 + expect(app.restApi()).toBeDefined(); +}); + +// -------------------------------------------------------------- +// Test for error with deployLambda=true and +// lambdaFunctionProps=undefined (not supplied by user). +// -------------------------------------------------------------- +test('Error on deployLambda=true and lambdaFunctionProps=undefined', () => { + // Initial Setup + const stack = new Stack(); + const props: ApiGatewayToLambdaProps = { + deployLambda: true + }; + const app = () => { + new ApiGatewayToLambda(stack, 'test-apigateway-lambda', props); + }; + // Assertion 1 + expect(app).toThrowError(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.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-apigateway-sqs/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-apigateway-sqs/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/.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-apigateway-sqs/README.md b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md new file mode 100644 index 000000000..cdfb9e4fd --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/README.md @@ -0,0 +1,86 @@ +# aws-apigateway-sqs 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/aws-apigateway-sqs/| +|:-------------|:-------------| +
+ +| **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`| + +## Overview + +This AWS Solutions Konstruk 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'); + +new ApiGatewayToSqs(stack, 'ApiGatewayToSqsPattern', { + apiGatewayProps: {}, + queueProps: {}, + encryptionKeyProps: {}, + deployDeadLetterQueue?: true, + maxReceiveCount?: 3 +}); + +``` + +## Initializer + +``` text +new ApiGatewayToSqs(scope: Construct, id: string, props: ApiGatewayToSqsProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`ApiGatewayToSqsProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|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.| + +## 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.| + +## Sample API Usage + +| **Method** | **Request Path** | **Request Body** | **Queue Action** | **Description** | +|:-------------|:----------------|-----------------|-----------------|-----------------| +|GET|`/`| |`sqs::ReceiveMessage`|Retrieves a message from the queue.| +|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| + +## 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-konstruk/aws-apigateway-sqs/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..60eecaed4dd40571389370a71f859f2560664ea5 GIT binary patch literal 98229 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts new file mode 100644 index 000000000..bac3b0681 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/lib/index.ts @@ -0,0 +1,240 @@ +/** + * 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 * 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 { Construct } from '@aws-cdk/core'; +import * as cdk from '@aws-cdk/core'; + +/** + * @summary The properties for the ApiGatewayToSqs class. + */ +export interface ApiGatewayToSqsProps { + /** + * Optional user-provided props to override the default props for the API Gateway. + * + * @default - Default properties are used. + */ + readonly apiGatewayProps?: api.RestApiProps | any + /** + * Optional user-provided props to override the default props for the queue. + * + * @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. + * + * @default - required field. + */ + readonly deployDeadLetterQueue?: boolean, + /** + * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * + * @default - required only if deployDeadLetterQueue = true. + */ + readonly maxReceiveCount?: number, + /** + * Whether to deploy an API Gateway Method for Create operations on the queue (i.e. sqs:SendMessage). + * + * @default - false + */ + readonly allowCreateOperation?: boolean, + /** + * API Gateway Request template for Create method, required if allowCreateOperation set to true + * + * @default - None + */ + readonly createRequestTemplate?: string, + /** + * Whether to deploy an API Gateway Method for Read operations on the queue (i.e. sqs:ReceiveMessage). + * + * @default - false + */ + readonly allowReadOperation?: boolean, + /** + * Whether to deploy an API Gateway Method for Delete operations on the queue (i.e. sqs:DeleteMessage). + * + * @default - false + */ + readonly allowDeleteOperation?: boolean +} + +/** + * @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; + + /** + * @summary Constructs a new instance of the ApiGatewayToSqs class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {ApiGatewayToSqsProps} props - user provided props for the construct. + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: ApiGatewayToSqsProps) { + super(scope, id); + + // Setup the encryption key + this.encryptionKey = defaults.buildEncryptionKey(scope, 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(scope, 'deadLetterQueue', { + encryptionKey: this.encryptionKey, + queueProps: props.queueProps + }); + dlqi = defaults.buildDeadLetterQueue({ + deadLetterQueue: dlq, + maxReceiveCount: (props.maxReceiveCount) ? props.maxReceiveCount : 3 + }); + } + + // Setup the queue + this.queue = defaults.buildQueue(scope, 'queue', { + encryptionKey: this.encryptionKey, + queueProps: props.queueProps, + deadLetterQueue: dlqi + }); + + // Setup the API Gateway + this.apiGateway = defaults.GlobalRestApi(this); + + // Setup the API Gateway role + this.apiGatewayRole = new iam.Role(this, 'api-gateway-role', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com') + }); + + // 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) { + const createRequestTemplate = "Action=SendMessage&MessageBody=$util.urlEncode(\"$input.body\")"; + this.addActionToPolicy("sqs:SendMessage"); + this.addMethod(this.apiGateway.root, createRequestTemplate, "POST"); + } + // Read + if (!props.allowReadOperation || props.allowReadOperation === true) { + const getRequestTemplate = "Action=ReceiveMessage"; + this.addActionToPolicy("sqs:ReceiveMessage"); + this.addMethod(this.apiGateway.root, getRequestTemplate, "GET"); + } + // Delete + if (props.allowDeleteOperation && props.allowDeleteOperation === true) { + const deleteRequestTemplate = "Action=DeleteMessage&ReceiptHandle=$util.urlEncode($input.params('receiptHandle'))"; + this.addActionToPolicy("sqs:DeleteMessage"); + this.addMethod(apiGatewayResource, deleteRequestTemplate, "DELETE"); + } + } + + private addActionToPolicy(action: string) { + this.apiGatewayRole.addToPolicy(new iam.PolicyStatement({ + resources: [ + this.queue.queueArn + ], + actions: [ `${action}` ] + })); + } + + private addMethod(apiResource: api.IResource, requestTemplate: string, apiMethod: string) { + // Add the integration + const apiGatewayIntegration = new api.AwsIntegration({ + service: "sqs", + path: `${cdk.Aws.ACCOUNT_ID}/${this.queue.queueName}`, + integrationHttpMethod: "POST", + options: { + passthroughBehavior: api.PassthroughBehavior.NEVER, + credentialsRole: this.apiGatewayRole, + requestParameters: { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + requestTemplates: { + "application/json": requestTemplate + }, + integrationResponses: [ + { + statusCode: "200" + }, + { + statusCode: "500", + responseTemplates: { + "text/html": "Error" + }, + selectionPattern: "500" + } + ] + } + }); + + // Add the method to the resource + apiResource.addMethod(apiMethod, apiGatewayIntegration, { + authorizationType: api.AuthorizationType.IAM, + methodResponses: [ + { + statusCode: "200", + responseParameters: { + "method.response.header.Content-Type": true + } + }, + { + statusCode: "500", + responseParameters: { + "method.response.header.Content-Type": true + }, + } + ] + }); + } + + /** + * @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-konstruk/aws-apigateway-sqs/package.json new file mode 100644 index 000000000..bc71d33cd --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/package.json @@ -0,0 +1,80 @@ +{ + "name": "@aws-solutions-konstruk/aws-apigateway-sqs", + "version": "0.8.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" + }, + "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 run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.konstruk.services.apigatewaysqs", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "apigatewaysqs" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.ApiGatewaySqs", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap new file mode 100644 index 000000000..e1ea9320a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/__snapshots__/apigateway-sqs.test.js.snap @@ -0,0 +1,1720 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test deployment w/ DLQ 1`] = ` +Object { + "Outputs": Object { + "apigatewaysqsRestApiEndpointD55C9F0A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "/", + ], + ], + }, + }, + }, + "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": "*", + }, + 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", + }, + "apigatewaysqsApiAccessLogGroup4D14D1D7": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "apigatewaysqsLambdaRestApiAccount8FA59342": Object { + "DependsOn": Array [ + "apigatewaysqsRestApi03BFD711", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsRestApi03BFD711": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "apigatewaysqsRestApiDeployment823C310Bb49e111d67a99651ecb38dc4ffc69e8b": Object { + "DependsOn": Array [ + "apigatewaysqsRestApiGET13C64342", + "apigatewaysqsRestApimessageDELETE46195B92", + "apigatewaysqsRestApimessageC2D606D3", + "apigatewaysqsRestApiPOST3638C367", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsApiAccessLogGroup4D14D1D7", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "apigatewaysqsRestApiDeployment823C310Bb49e111d67a99651ecb38dc4ffc69e8b", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "apigatewaysqsRestApiGET13C64342": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "GET", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=ReceiveMessage", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsRestApiPOST3638C367": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "POST", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=SendMessage&MessageBody=$util.urlEncode(\\"$input.body\\")", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsRestApiUsagePlan744FD0EB": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "Stage": Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "apigatewaysqsRestApimessageC2D606D3": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "PathPart": "message", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "apigatewaysqsRestApimessageDELETE46195B92": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "DELETE", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=DeleteMessage&ReceiptHandle=$util.urlEncode($input.params('receiptHandle'))", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Ref": "apigatewaysqsRestApimessageC2D606D3", + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsapigatewayrole2BA120D3": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsapigatewayroleDefaultPolicyD83F1724": 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 [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:ReceiveMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:DeleteMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "apigatewaysqsapigatewayroleDefaultPolicyD83F1724", + "Roles": Array [ + Object { + "Ref": "apigatewaysqsapigatewayrole2BA120D3", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deadLetterQueue3F848E28": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "queue276F7297": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "deadLetterQueue3F848E28", + "Arn", + ], + }, + "maxReceiveCount": 3, + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; + +exports[`Test deployment w/o DLQ 1`] = ` +Object { + "Outputs": Object { + "apigatewaysqsRestApiEndpointD55C9F0A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "/", + ], + ], + }, + }, + }, + "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": "*", + }, + 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", + }, + "apigatewaysqsApiAccessLogGroup4D14D1D7": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "apigatewaysqsLambdaRestApiAccount8FA59342": Object { + "DependsOn": Array [ + "apigatewaysqsRestApi03BFD711", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsRestApi03BFD711": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "apigatewaysqsRestApiDeployment823C310Bb49e111d67a99651ecb38dc4ffc69e8b": Object { + "DependsOn": Array [ + "apigatewaysqsRestApiGET13C64342", + "apigatewaysqsRestApimessageDELETE46195B92", + "apigatewaysqsRestApimessageC2D606D3", + "apigatewaysqsRestApiPOST3638C367", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsApiAccessLogGroup4D14D1D7", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "apigatewaysqsRestApiDeployment823C310Bb49e111d67a99651ecb38dc4ffc69e8b", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "apigatewaysqsRestApiGET13C64342": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "GET", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=ReceiveMessage", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsRestApiPOST3638C367": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "POST", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=SendMessage&MessageBody=$util.urlEncode(\\"$input.body\\")", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsRestApiUsagePlan744FD0EB": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "Stage": Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "apigatewaysqsRestApimessageC2D606D3": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "PathPart": "message", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "apigatewaysqsRestApimessageDELETE46195B92": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "DELETE", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=DeleteMessage&ReceiptHandle=$util.urlEncode($input.params('receiptHandle'))", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Ref": "apigatewaysqsRestApimessageC2D606D3", + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsapigatewayrole2BA120D3": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsapigatewayroleDefaultPolicyD83F1724": 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 [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:ReceiveMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:DeleteMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "apigatewaysqsapigatewayroleDefaultPolicyD83F1724", + "Roles": Array [ + Object { + "Ref": "apigatewaysqsapigatewayrole2BA120D3", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deadLetterQueue3F848E28": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "queue276F7297": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "deadLetterQueue3F848E28", + "Arn", + ], + }, + "maxReceiveCount": 3, + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; + +exports[`Test minimal deployment 1`] = ` +Object { + "Outputs": Object { + "apigatewaysqsRestApiEndpointD55C9F0A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "/", + ], + ], + }, + }, + }, + "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": "*", + }, + 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", + }, + "apigatewaysqsApiAccessLogGroup4D14D1D7": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "apigatewaysqsLambdaRestApiAccount8FA59342": Object { + "DependsOn": Array [ + "apigatewaysqsRestApi03BFD711", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "apigatewaysqsLambdaRestApiCloudWatchRoleB51EDA01": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsRestApi03BFD711": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "apigatewaysqsRestApiDeployment823C310B94660621187ecc4f60bbf8643bc7537d": Object { + "DependsOn": Array [ + "apigatewaysqsRestApiGET13C64342", + "apigatewaysqsRestApimessageC2D606D3", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsApiAccessLogGroup4D14D1D7", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "apigatewaysqsRestApiDeployment823C310B94660621187ecc4f60bbf8643bc7537d", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "apigatewaysqsRestApiGET13C64342": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "GET", + "Integration": Object { + "Credentials": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsapigatewayrole2BA120D3", + "Arn", + ], + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/json": "Action=ReceiveMessage", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/", + Object { + "Ref": "AWS::AccountId", + }, + "/", + Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "QueueName", + ], + }, + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "apigatewaysqsRestApiUsagePlan744FD0EB": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + "Stage": Object { + "Ref": "apigatewaysqsRestApiDeploymentStageprodAA3C7DD5", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "apigatewaysqsRestApimessageC2D606D3": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "apigatewaysqsRestApi03BFD711", + "RootResourceId", + ], + }, + "PathPart": "message", + "RestApiId": Object { + "Ref": "apigatewaysqsRestApi03BFD711", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "apigatewaysqsapigatewayrole2BA120D3": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "apigatewaysqsapigatewayroleDefaultPolicyD83F1724": 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 [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + Object { + "Action": "sqs:ReceiveMessage", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "apigatewaysqsapigatewayroleDefaultPolicyD83F1724", + "Roles": Array [ + Object { + "Ref": "apigatewaysqsapigatewayrole2BA120D3", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "deadLetterQueue3F848E28": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "queue276F7297": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "deadLetterQueue3F848E28", + "Arn", + ], + }, + "maxReceiveCount": 3, + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts new file mode 100644 index 000000000..2fdbf63a3 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/apigateway-sqs.test.ts @@ -0,0 +1,108 @@ +/** + * 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 { ApiGatewayToSqs } from '../lib'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test minimal deployment +// -------------------------------------------------------------- +test('Test minimal deployment', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new ApiGatewayToSqs(stack, 'api-gateway-sqs', { + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ DLQ +// -------------------------------------------------------------- +test('Test deployment w/ DLQ', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new ApiGatewayToSqs(stack, 'api-gateway-sqs', { + apiGatewayProps: {}, + queueProps: {}, + encryptionKeyProps: {}, + createRequestTemplate: "{}", + allowCreateOperation: true, + allowReadOperation: true, + allowDeleteOperation: true, + deployDeadLetterQueue: true + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/o DLQ +// -------------------------------------------------------------- +test('Test deployment w/o DLQ', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new ApiGatewayToSqs(stack, 'api-gateway-sqs', { + apiGatewayProps: {}, + queueProps: {}, + encryptionKeyProps: {}, + createRequestTemplate: "{}", + allowCreateOperation: true, + allowReadOperation: false, + allowDeleteOperation: true, + deployDeadLetterQueue: false + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike("AWS::ApiGateway::Method", { + HttpMethod: "GET", + AuthorizationType: "AWS_IAM" + }); + // Assertion 3 + expect(stack).toHaveResourceLike("AWS::ApiGateway::Method", { + HttpMethod: "POST", + AuthorizationType: "AWS_IAM" + }); + // Assertion 4 + expect(stack).toHaveResourceLike("AWS::ApiGateway::Method", { + HttpMethod: "DELETE", + AuthorizationType: "AWS_IAM" + }); +}); + +// -------------------------------------------------------------- +// Test the getter methods +// -------------------------------------------------------------- +test('Test the getter methods', () => { + // 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(); + // Assertion 2 + expect(pattern.sqsQueue()).toBeDefined(); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json new file mode 100644 index 000000000..da2041d5e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.expected.json @@ -0,0 +1,634 @@ +{ + "Description": "Integration Test for aws-apigateway-sqs", + "Resources": { + "testapigatewaysqsRestApi557C7EDC": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "testapigatewaysqsRestApiDeploymentCA19D372c3b49be7d97efd65e90b7b5084cd80d8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testapigatewaysqsRestApiGET4AA265C9", + "testapigatewaysqsRestApimessageDELETE2D4539B7", + "testapigatewaysqsRestApimessage6D62B7B0", + "testapigatewaysqsRestApiPOST26D15DBA" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testapigatewaysqsRestApiDeploymentStageprod1C007159": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaysqsApiAccessLogGroup37AB0350", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testapigatewaysqsRestApiDeploymentCA19D372c3b49be7d97efd65e90b7b5084cd80d8" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testapigatewaysqsRestApimessage6D62B7B0": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testapigatewaysqsRestApi557C7EDC", + "RootResourceId" + ] + }, + "PathPart": "message", + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + } + } + }, + "testapigatewaysqsRestApimessageDELETE2D4539B7": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "DELETE", + "ResourceId": { + "Ref": "testapigatewaysqsRestApimessage6D62B7B0" + }, + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaysqsapigatewayrole07110CD6", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + "RequestTemplates": { + "application/json": "Action=DeleteMessage&ReceiptHandle=$util.urlEncode($input.params('receiptHandle'))" + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":sqs:path/", + { + "Ref": "AWS::AccountId" + }, + "/", + { + "Fn::GetAtt": [ + "queue276F7297", + "QueueName" + ] + } + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaysqsRestApiPOST26D15DBA": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "POST", + "ResourceId": { + "Fn::GetAtt": [ + "testapigatewaysqsRestApi557C7EDC", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaysqsapigatewayrole07110CD6", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + "RequestTemplates": { + "application/json": "Action=SendMessage&MessageBody=$util.urlEncode(\"$input.body\")" + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":sqs:path/", + { + "Ref": "AWS::AccountId" + }, + "/", + { + "Fn::GetAtt": [ + "queue276F7297", + "QueueName" + ] + } + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaysqsRestApiGET4AA265C9": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Fn::GetAtt": [ + "testapigatewaysqsRestApi557C7EDC", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaysqsapigatewayrole07110CD6", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + "RequestTemplates": { + "application/json": "Action=ReceiveMessage" + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":sqs:path/", + { + "Ref": "AWS::AccountId" + }, + "/", + { + "Fn::GetAtt": [ + "queue276F7297", + "QueueName" + ] + } + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaysqsRestApiUsagePlan2295EB95": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + "Stage": { + "Ref": "testapigatewaysqsRestApiDeploymentStageprod1C007159" + }, + "Throttle": {} + } + ] + } + }, + "testapigatewaysqsApiAccessLogGroup37AB0350": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaysqsLambdaRestApiCloudWatchRoleF10D0F78": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testapigatewaysqsLambdaRestApiAccountACC6BE82": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testapigatewaysqsLambdaRestApiCloudWatchRoleF10D0F78", + "Arn" + ] + } + }, + "DependsOn": [ + "testapigatewaysqsRestApi557C7EDC" + ] + }, + "testapigatewaysqsapigatewayrole07110CD6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testapigatewaysqsapigatewayroleDefaultPolicy052E7AD5": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + }, + { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + }, + { + "Action": "sqs:ReceiveMessage", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + }, + { + "Action": "sqs:DeleteMessage", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testapigatewaysqsapigatewayroleDefaultPolicy052E7AD5", + "Roles": [ + { + "Ref": "testapigatewaysqsapigatewayrole07110CD6" + } + ] + } + }, + "EncryptionKey1B843E66": { + "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" + }, + "deadLetterQueue3F848E28": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + }, + "queue276F7297": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "deadLetterQueue3F848E28", + "Arn" + ] + }, + "maxReceiveCount": 3 + } + } + } + }, + "Outputs": { + "testapigatewaysqsRestApiEndpointD98015FF": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testapigatewaysqsRestApi557C7EDC" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testapigatewaysqsRestApiDeploymentStageprod1C007159" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts new file mode 100644 index 000000000..e913a8800 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.apigateway-sqs-crud.ts @@ -0,0 +1,37 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-sqs'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-sqs'; + +// Definitions +const props: ApiGatewayToSqsProps = { + apiGatewayProps: {}, + queueProps: {}, + encryptionKeyProps: {}, + allowReadOperation: true, + allowCreateOperation: true, + allowDeleteOperation: true, + createRequestTemplate: "{\r\n \"QueueUrl\": \"${QueueUrl}\",\r\n \"MessageBody\": \"${MessageBody}\"\r\n}" +}; + +new ApiGatewayToSqs(stack, 'test-api-gateway-sqs', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..297d2a80e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.expected.json @@ -0,0 +1,445 @@ +{ + "Description": "Integration Test for aws-apigateway-sqs", + "Resources": { + "testapigatewaysqsdefaultRestApi554243C3": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "testapigatewaysqsdefaultRestApiDeploymentFB9688F515062e1c331d835b42c02aa42f85db7f": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testapigatewaysqsdefaultRestApiGET733E6394", + "testapigatewaysqsdefaultRestApimessage41073D7F" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testapigatewaysqsdefaultRestApiDeploymentStageprod600FEEE2": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testapigatewaysqsdefaultApiAccessLogGroup16132600", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testapigatewaysqsdefaultRestApiDeploymentFB9688F515062e1c331d835b42c02aa42f85db7f" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testapigatewaysqsdefaultRestApimessage41073D7F": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testapigatewaysqsdefaultRestApi554243C3", + "RootResourceId" + ] + }, + "PathPart": "message", + "RestApiId": { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + } + } + }, + "testapigatewaysqsdefaultRestApiGET733E6394": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "ResourceId": { + "Fn::GetAtt": [ + "testapigatewaysqsdefaultRestApi554243C3", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "Credentials": { + "Fn::GetAtt": [ + "testapigatewaysqsdefaultapigatewayrole080B85EC", + "Arn" + ] + }, + "IntegrationHttpMethod": "POST", + "IntegrationResponses": [ + { + "StatusCode": "200" + }, + { + "ResponseTemplates": { + "text/html": "Error" + }, + "SelectionPattern": "500", + "StatusCode": "500" + } + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + "RequestTemplates": { + "application/json": "Action=ReceiveMessage" + }, + "Type": "AWS", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":sqs:path/", + { + "Ref": "AWS::AccountId" + }, + "/", + { + "Fn::GetAtt": [ + "queue276F7297", + "QueueName" + ] + } + ] + ] + } + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "200" + }, + { + "ResponseParameters": { + "method.response.header.Content-Type": true + }, + "StatusCode": "500" + } + ] + } + }, + "testapigatewaysqsdefaultRestApiUsagePlan3475CA67": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + }, + "Stage": { + "Ref": "testapigatewaysqsdefaultRestApiDeploymentStageprod600FEEE2" + }, + "Throttle": {} + } + ] + } + }, + "testapigatewaysqsdefaultApiAccessLogGroup16132600": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testapigatewaysqsdefaultLambdaRestApiCloudWatchRole8EA3C5EC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testapigatewaysqsdefaultLambdaRestApiAccountF7D19F4F": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testapigatewaysqsdefaultLambdaRestApiCloudWatchRole8EA3C5EC", + "Arn" + ] + } + }, + "DependsOn": [ + "testapigatewaysqsdefaultRestApi554243C3" + ] + }, + "testapigatewaysqsdefaultapigatewayrole080B85EC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testapigatewaysqsdefaultapigatewayroleDefaultPolicyFF253592": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + }, + { + "Action": "sqs:ReceiveMessage", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testapigatewaysqsdefaultapigatewayroleDefaultPolicyFF253592", + "Roles": [ + { + "Ref": "testapigatewaysqsdefaultapigatewayrole080B85EC" + } + ] + } + }, + "EncryptionKey1B843E66": { + "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" + }, + "deadLetterQueue3F848E28": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + }, + "queue276F7297": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "deadLetterQueue3F848E28", + "Arn" + ] + }, + "maxReceiveCount": 3 + } + } + } + }, + "Outputs": { + "testapigatewaysqsdefaultRestApiEndpointE6DCCE4E": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testapigatewaysqsdefaultRestApi554243C3" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testapigatewaysqsdefaultRestApiDeploymentStageprod600FEEE2" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts new file mode 100644 index 000000000..3da9f042d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-apigateway-sqs/test/integ.no-arguments.ts @@ -0,0 +1,33 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-apigateway-sqs-default'); +stack.templateOptions.description = 'Integration Test for aws-apigateway-sqs'; + +// Definitions +const props: ApiGatewayToSqsProps = { + apiGatewayProps: {}, + queueProps: {}, + encryptionKeyProps: {} +}; + +new ApiGatewayToSqs(stack, 'test-api-gateway-sqs-default', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-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-cloudfront-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-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/aws-cloudfront-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-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-cloudfront-apigateway-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md new file mode 100644 index 000000000..2f410cda8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/README.md @@ -0,0 +1,82 @@ +# aws-cloudfront-apigateway-lambda 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/aws-cloudfront-apigateway-lambda/| +|:-------------|:-------------| +

+ +| **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`| + +This AWS Solutions Konstruk 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'; + +const stack = new Stack(); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + lambdaFunctionProps: lambdaProps, + deployLambda: true +}); +``` + +## Initializer + +``` text +new CloudFrontToApiGatewayToLambda(scope: Construct, id: string, props: CloudFrontToApiGatewayToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`CloudFrontToApiGatewayToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Retruns 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.| + +## 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-konstruk/aws-cloudfront-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..9ec45f01767b690aaf9ffeeb892859ae5e8c689c GIT binary patch literal 127105 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
Sw^! zaKY19k6yGn^=czJfm=^?%YKf2Z)Z;z#tNQ;5ZblV6x3%s84T60t6DcAKi95Wnl=_t zU(0@i(PIhNT10rp>wb(Qd1pgarq2M3QT$FT|6p}-Wp)UBi6cp}i^f0(8-^r6_)4`5 z5>XEXi&#z!NIu*HTQ$VK=BDx+84QppOC3hRrGa7u9eZEG?f*X-*GCy6CaaPr`ViD2&N@#LVHX&UBWmZ+R=B; z$*r#0`%%bi0^tzgi;^~`9((CBg2DTU>vZv#cy&74<8-Z$_&ws6mT!nUKb zHQ(rc6STzXj}zjHw@qu+22E>SJ{{=x%SpSl{MwI@RJHTLn=?blexd zKZ!__)9&@a+}b(e^aVc9$@)4Jn>Xi}1f6_wJ4$EmO2|Mu>@LW$I@eG%D$de=WN%_x z{Bt|QA|v#}G8mUdcw>cl%>o755S5xgZ=iYWQoFE&vnJGn^XT%;6WU@rM z3S@Ok5y%h#`a1Z!Hb({QnOdw2(qMu?(7Z|zN=2PoHhawqh0C*kiDsb4 z5Vd<+S>hBnfA)$MXW!;vj=sr*pNOHng58xDOg+s0R2R|v3->4_-H;Bo4>U~mj8f(E z7untaNzBj4u188Hn)h<*dgMXdkQX}k>LLG#LEBg%zOmlzw-bUVh7IH$;Ycn# zma-1cC+28|48b=l=hqQEkYU`H3MUY^D0zK3hl<^4EQA!l_7eQ{fo`+h-8tdvb63Q1 zk~=JngV;k___*>1L7*220aoj(@ujQvI-qtQJQroGGR`RwOssKmFt4Pl>!$9e&uq{5 z8r5&_@SzQHyC3~DWZn8J$r~x;>6~9wFOo)tz`df=aY>q?U0x9%b96KD4B9xxMiJ<7 z8ls&IXXCO7%|&O937VCKM|i1H99vWJ9fxsUGDYP#)^y2={gWDH^YmY2e`17!R{~?t z>;7VZ!*nLBT9c}qZ5^wCYrD^F73bRWyG_`Jv+7-C1PD39k{Z}*0eH<&4U~a97%`f- zQM9_B52#=Ji6ynZvNKuWx;UNQ>`d@jii@EzYHQEXll3b4+YL2d1Xk9w^S)X8%T53Y zSVhLdvb$S41Qbbur0=2!86cb9($ZMz|BDaaiRb!lXUn8&w%-E;LO9ePK(F4qB7Akz z>Qi23fR_Lj{(oF0j{D$De5w+hVZa967E)6y(3awYxlc70Vl{u5-L%QwQomoVE#p3wr zAx2|$RW9!d9TBKuY~GB|e~>f-v_V&GfDh=xkLP{?Ryi>GZxe&@=V=3Jz}yKj)7wF^ z(3=1sDw$YfJNhGF+=akbf<1gOd^6XsM@cLP^KHL?xFn14Q^TAw63}NFYX;~&Ch>arRL|*b+x>p z_#SjW{clb(LA_pEdf%0MTR9W34}FkZ^Tl;~4s#lRZLuDu(#HEh={#ohTbk`AG=U6! zmeI2LZ_}<0CE)>HEAOC+JPglbDcZmh^2JWyElj?Sje`%XV2#L#EKTzFeLUt*X==pM?sUL)EV&KUYQ&9{W z+1;e6idlUMAfkMs$cJ<=0z(*t4cLYRO{lA$v+~4K?*IOyg!)8(7GA z)x}31i}cRti?<^`1VUT>`+aUx-mPtdyxEx6h`KzISA1;`_m^Oe8M&FWRcolF+}ux zG5lVT=aBW#Xbo&zfgJA%Qen|REM(8fhTHRDUkbuSBYJHs$ptiA#;RjKrM1h-v#0lG zlxyqa43!iz-TXh151P^MYqiXr2th$4A3dDS(n+n(^%AgaHzYL0_PMD{$-UxUnXwOi z$Ey?wEtM|~C}q+sEn_>qA-rTvJS=GNpY{Res5J?C#4w>wvT>z0ZJDsvuj^(7 zyDKGEMt0B7y^kHumo_oJ?-0DVbx!C0{Uv~aPHMpUOTsf9!YFG-yLNx`Z8T}S)9BCR zIPfy3h^^?l=kP%1mtyLz%)pG*!><3ewR?z!OYbaZ183X!IivD-(piooW z?c+Od3PRR?OHR;j%BBg-4sAiOnD7T-|ciJ|?Z8s-@TbZuSpW6Pv9&W|Y{1xeZ$X{chw_Rn56@%5u^N_D3uP{o z3B1gG8tDnq+-g2LS}2E8kfb+HJ+Yo-D?=7-IZo->nm_~4`aPDov5L|VA1~in9M*3v z2z*(0!CK|q?K#(Qxvh$PWqqVJ{#6v>B4vYK68J(mDKVIs=AbE4?=Of1<~JoO>m?k# zu~t_E%NM!pF#LaE=((fX1m3`|26U#=UNXGSI==OkEw9WJPyIz!mow3Ba}V6`7^kA! zdAw=XH8=OQ`IT?IRw>|h;YVa7DbSY3IKuO<{n&~yd2EsKQgFE{_#!+Dw%9^>DRn1o zF%7Pd1kQH;sFr$SsiZX8EVx_tHg&K82I8t>bAX}enPvDSCvbs7_krJk)r{{{?z}fD z`L(%P!#bn-nHU_z^)R3efT=yVb(Z3EkNEILHz2nId0e?=n5b;!_gDGpTgUd4sh~a@ zpKf!6n~Rb>;0Qz545Z#t_)CV*bVMa+IZz>#LWPL17zwTBo*x!lO}2AGEIxgDHSH$v zKeC2TCH>IN_MxUH?!kpMG)P(OFO z8DFGcKM{4WyRoU(cDeeryEp4e7TtVkgwiccxy%(~FXApnrBc|q$yn5G1n)bz8PA7W_A^&lMu6MHSTMvQN<%jn(*1O>S zI_J4@Cpz`d!-y?k|BHHyTmCT_&Hv?9RI;9WRx1UJ0zLL}wSyq1g~&{9LTSk0aBP?E z?^~skqnEMYER4VOR)Q$O`8I+6o*Ue)$31C1N%ZL_` znD$7S9%5KY&A(@e6xNud*RQSBC0owbiK6_95cQ`BnQE++M||b}Xo7KH8YnJGTqNe9 znaKVz*o~n{21J^=8ZUTsnpZKb_DVHWugw7f^24^Y@SXi_2bgdA7DVm#1!`ajy*w+s z(tonAHd!pYTb=m_`L;*OIE-y`_|L-{UNjvyBzUj7)mIh;_0QS|fB2=YSCV`^vxhBi zt}U4N%YAr^;l;9hG`Bbw&hsMRTfMxE3fHNy@F&KImoT%y%%aVs0_%)AxhNzMm8>y0 zc}+jlL^qG0#foyCfM($LC+IoF#Ju>n_#aw#G|s2IgL*CE-B!@{d(Zo)yyLORPusft zzJjJ{*>%;Y2kTFS{TCGeAB#B^oPXH#=WREQ4Z%NfjV4UtxfY- zBynh7LE!H~t!T5JnW(QgG*d=3?>rUp^EbTUitovOw`sLhbcZv4t~%B@bl=D`yl0H1 zIWH)-bXrc1M9}irI6)L}4xwPB5`Q=b&H5F#v^l<0U3g_a(0i65ZF$zMY!C*EH(v8v zSp^9rF^7>3{=gQ(^$U;tAtEf7**`L#3XyJ4!lsS_+Sq9G$|!E{ezWN?XW?xuxElE? z=RZuHfE2*;n;DCU{4eG^lbrowUowy|pP%_A6Rt7G`$hhdi@<%s`7Hy5zyL+Su=t|w zi6ZB%fBxNrk@tPI9V0!*&nwvGM#it#FSdKFapRH8ekR**jp*@@!i61*z^^|58d?KL zd@uKSUrvN53p}34WSs_SZ{PL3T=3T~V9V*T@LieM{P%kG2Et=Qn0OZ)vz+c0c?cHn z;-ur4!vl40dk?xn-lx+^)%2aw|GM+IR>D~m5;g45Px6ETAg?Z>2qiPub>!;yUmu#* zIVqZ!Utv984E6^M={NS(pBMkNSgS3N1Utj65Yp$7@xdQ2E$GNG<8ieI*8BIUOptdBLvmO04D}x%XyK(xdU-bh4cw)q6k$-I`<^Vuv9q}@0%M?}e zbCOLQPlyWX=hJ#Z$*vS-`^pldMsd2HOW)h?&s7IoHbC?wj9fO1UVGFi^(kFe`n=Dn zxX^AHTWdE3(~R?E+vB~>0xb(&ua}ff&yKo=aX~Fn`>l4X8+uQ5AnT9*hh>gb(F7eq zY}APpzn;-butL9Py)BH+_RBqd%v@>T$CE-V{7VfX2GhSVXaI;%F!hiD7D^!kWkK~0 z51KIbSRa*!V>n?9drS3|Q<#vC;QGbPo~g>mG=j(LeZ+DE3`7|M6%# zN9d+@8~z}K4~rc9CfJ&aN+exPpfVRDC_JRiT`!k@{xK2zYsD0?b$}V+FOJw6k48Za z@NClcy*Wi$@K>`_%VUbw#h#tsxt~q{B8Jx2>*eP0DLrFiue+$n&6JNJhWA-Z{M9w7 zobF}+&V604RfKm#lnib+vdR3Iyk^DuXNFa+@5vi1DpH@~|6y3mHpf?JsHGo`iKE%R z-=SXRy@L+aSL!Jz zF>R;?yz)A4^-0R@525W zv|T9h58UjPK2sKYhRotuK3ZKU$YHD{Ud|2<(F`Zr6>4~#c)|egi)LGVG7-dZd*YVN zOY@eqH>?>D32sf!GMWWnJfcy9Y6*$i{Ow7`Q0l*|Fym_hl>v?bN)o3>qVDqDhj>b< zIziK!+1w;Pv>#!2{Q^TR7V6ds$&R6r^eZ^6(L}k8gmW1onSZJo&e9tS8Js%W+Cgw8 z@yw&GU!e(E%Ln`E$o~4rsiwK(FLBs4;H1u-NvFcE#`ltP%`8w0Zi?Jjp=?@Sy`7z1 zU~lOg{zIeVsMJ|!Lll~o1?zGMUxGB@cgILRpvMw?gMTM~3Pqb%Ecp1{jkcf!&Ksm2gc(Xne`oFQ z?=F`1TiiCf&xgd^q=*IyBVgBlv0PKU2g5{q%pdfvx`(Bc`h=(du>q*$6{O7^o)^HoG4o))mRzK6aapsK;Yp^) zc*--c;dmecwm1w<$bnoxm7H~^t*$ETd);8u7r8-$A2f%2mWlPd5BByXb;Q}3EA(#1 z5eEXCr`8{n5V`O!8wsrOoPBToq)9R1nBnx(7XaI5wR>E(kRl$H|Lz-05cQV7JCUhl zK6TX-^dVcCr4^`(X!Y7&#)Jy3aRzHuEDnRGH--qQvE}dH*_E!*)%&>Bd1Hz0uPg6& z_QxW(P3x9)k;mBB`$=K6LaPb2jn-g%tShgBPv(%RoG{O?+U4w<^kCO9USL-%?uu-j z!Y|?5f|OQ%-@$&1VQnchM*FRxAkueflv*64>Sz07@UMt;(N1$T^BGa$a%brkKeuHv zIEHaE_lI@35PJ^jUmwr3gG-11-7*n!LS!t(tb}a3M}f}U$HUP%^&9juI*>h%N1eWS zd#Ow5xZ2(s$nkOA-K*=3PG0q?{CAVWD-87$`%t|?bE`f5l#Z#wI^Nfc+LhZgc z1i1qP%;3`+3F~joiUxEEnt3`C(?&7>F2=4y%UdQ2Pu@FI2)An7sDn9&x(uSgKJF5) z7ja3f^$E7mn&qE)XL;6ppN)U2c{d>$jV6TqR6nA59tLf1gwgZhl>tRcyie4>tU`ndni6u4wP-F~Ko6?&@FV~eHVG%d|sPS`q>R;)C4!4(KB zCzmR>vj%o5`v}!e6|Y|iOv+zCW1*5=kr|k97bCBjO*`27B#eg^Gl|Wd)@`R=@4)ag z=$x6e>GBb6<~gE=jA~#L665k(%w#al@COoQ9bSmzniz9^&JU(xu*WYU%)U#1tIKit zc7r(#$JULyr3Te^Q_e~2!cd(d)0l7I;{xUda%mES2pcb+OQN#;`k+3o&^|+@yYbxD((O-qPdfmoFZ?txAai=UY78pIDZSMQv_0+7M z-Ic?Cf{F0SR+UBu7rb|p(pvrFv9jNfA(G|4M-2q~z&zTpx$+pbJ6}GkCS^ySKe}I) z+3HLo1QoVm21V$MVfdZucekyUrt*w`NWyL0)|IAA&Cup-)Hmrgo2GQWMj(ybZ72qDvv{l)i?bh$ z;$3L%NqQJjD$Vo28u`Gfi98Dt9$Xc+Ofr0dw8BG8kLxjO$5@%f{y4$kpUVQiE>u}f zd*+0KxDN9CY|+gx^i0C1W8|AJGqE-l8SCK_%-a#+f@E#;q+o&AE!(+54=om)AFucv z^@kk(RV98iFvRcb1pQ(hMPeoakWxo_lSUY?iiN_~ z0TdS6H~c^a<&rT%4dx}{R9M-ea@huUoGP+y6AY9qSV#r^Ob3{#de_U zwx)TNbJHsW31#$2&u?aJ{=+Pf|0Ky5g*7z7`$iO}r=`Tw)$hyQ%@KC$7uoD zOb?Mr&+W&G7m7b1{M6cg^XYN(gb)K-SO^OIL|oeFoKk|>cL>L-)?ngkP%LV+CWra) zQ5-djzD_1GpN)nLD>mP`#hKyaar6DiV@bSb>(@(g0FxX(!E)vSxNGgCs`~U1q6T?@px7+@$eoEI}JAh6eBpq%vQV4K7(7p7s6(G=BXqaNV7 z)_tY2B|p@LU$$svz&uk?LN=4;5BoS)VsMJzrjfzT^;f*z!mdPdb)SYx&wKgNYs)Zc z>TMg+`_*q;T0S>+U5aYc-3klB)(o#S4xDN3c##kDbxwV140K^E1q)b5`5$Vw7xA779&-dSY}2SN(ZCqwPn}Ktbu~Dsq8X zI(%3%R-lsWmY?@sw3h(X-KI8Rd8q27NuSq%_V+>`hUCIZA#N1htw{l~H|+@ZmUl+B zF)1lQw8v)T?OgW;W=&UtGi?nF8}PiFh#|-8E$cw$!`|)XBG_F`f}+0Nhjs$8VU@*! zJh&^8i1(-2<&vHv^O39Hb;jA(M>H#Db5{EjA}Vure!MygJm#3sAjHC1cf*~#^bD{V zQ+xfy_hYQ?YVqlOc;r1L(ELyE&P4rD^FPFMDnq?6MI|!Bn37acDh929Tx35lLK#Po zI^q>KAoNcQk!`*O&1{#l929c?9-r$QpJK2|X0U(r6(EVK@uDh!j%&TIZ7 z#%pB2$5u_((z2nG>V9uE-m@ZYKSuy}0ln(|Vd2kAU;9 zEY~d~H1Y!+@o6p&UcRs}{j_bAzK(-Gqdiz=6!Pc{&1wq#SpuAoo7Y(`G0$7YL{#|u zYextnJ#~y6Pwg)?$F`NAX6eKBfb@srAHcuq&e3Te-HqwICeg9VKUHd=3G{ue%caL&_k6^&R({&9-dPu#5?~XW7#{G!y=jvZ#Zy9JiXsIMqlzMNHU_o_#=sB>kdzaeyLxI}mA}+_ z^xbf57AVE&mqQC#kSABV84-1YR1PJ?1%1}~Ba><%7YlnQwgu%ZFf4x$os?p|**vnD z@_G!C(Jr{$*&T9X<+vYX$l22qF-%XTmXbN3=E?16J=Me7$Cq@a=O4iVosGJmClk>}U#xkdw(jEG#_+1<$oM zfn}ZJ_<~*P9;2G=iI6cvmsMzlv2SMT#^6YOOUCdK(YD5uNzQd;x;DdZSadyTN|H)?j5Krs%E#B|MwhUL^Q#E7iA2s|2HtodD$V{kG zP-wqNzZ$@;{Rx+IK4xoDG_72PDvczKvTfY1<4UMRVoa35U@i_RQ#rh8$r;%Do2M+# zt?cTq5fl}|9`RLKCN9qEZGrsl&=Oap0RkUChfRWjnh1oaL(i=Gi1U=;GqAj_({kQ` zv{h)lrLkn>Bn-};_-B*Js_5k#As*P^=vLkoXjoJ05I6l0Tt`Jj zodtr}uty*sq-}d4%jDBqUfijkA0R#3iwQqLiU4VThRVrYxJ6)a?&S z+zhDGhSsvyL1m{o+ph9X`T%v6f%Z#U%w6*CN#hm!w+7d}epdPoFTmVV4oNcwSXsg=F{bd;56smV2%Dj^8xHc(Rf{%^M*{UdJB7Thh_a z3+AW(ruDpIdZqpheJDv(K3(Y@xY3@(>1|!ZVpH;ng)H5wgk`qnTt-^eGPZyHSRM!P zjbIKS>Vhdol6zhEQ5R?5M`t{vAaMX%?w@lA-KKuV%I9}axT)3WUmf|}NFR}){}wZ_ zfRgxD{b0Xlq$82?1uIn#MXFu*fiWwQnhrk&Tz4WoN-FbFHSMN%~A^(dg0r6+cc&_C)O{FMVAt!0BsE zS0j@;WEuJe?nZ`)av&dmG{=#WSW!_!gFHr*)82vWUeQ_!lIW!uht6~Rj`3@yXTOuu z6_-%!t?Q;@D9v{LOq0C|(iyW< zjB*^aoe@B@VTyjUafKl@Vtp&O%~hwd-Ioe!x;JCioyDsyWjko*DVLb_#^*CXK!faO z+DKv%mS5V&v)XBJ(X^T_iKJI{t5r|9%|6Ai(V-ArJi~E5RuV0@>BT_77CD<~5kdaJ zQ`KgV^`9E^ncn^>HJ^uv5#s$v0UUe?p2a{ImLN-Gr2Cq&99%jw3R}?#xt0w?>fs@h zlTkmoh_Zr@m9guM>ef&{^A*Q0Y;$rT!rP8;}q+?g7+Y**A_CVY*}NH%7+Q zyugrA9CT^)ocq+=Bt1agz+1ffj$zUfAn?};=ziX8jRIK@=PYrVF6C5O8B|Ez5n-vcx}-$l58dnKUs zzUSkXWaqt|?YL2Nfk}?ObM<*W;ujMv0i*ht%n85m`r6s9-))0$oX-|poNX>o&E`qJ z*M){5F(?U~fr@w+WvL6UI|Fa>`*24zoV$sFq@!LcemtYazV#8a4)ZXc?`F z)FDOZlvhxGvoe$WgNDHP;{3O@)(zKhB!Yt6WSTgQbt-0_VSvy88w=a z!(r4Nx;3Jdd9&D^hoPQ9ieRu)Ldh_qoTEJWU~=j@LGlJGM(1Ni`HIH_@3f8u`5yvz zq!N*|aIsuz*7YkCWTb292CD6OGSLl-CgM-ro(CQ%;ZyxxunOP3Rm&n{w^>u-i)pQ^ z2JxBCT?$S10%v8tin`-SB}-XemKYhN2x4z=a=wwJ<}|@stXB`O<9(a<-jTIdOC=u1 zs$t0S01zImPjAK?Y0%Lv>vZILU*uddYyp?O@DIHBJiJ%0W^U;97KZhV4s|M{hy*o| zxrW?=g}YyYFY$3G`|Pld6VggHfI`C@i{d6t*s^Sng{#{@pN~h={CJxG-Ti;cgMdg6 zgvg(qA3QXVZP-{=4~s2pv^q1x2uJb`~>b4wI5l*1t-Ohtj;m3g5)x!y5umU!GD1yI;%yI%GQUYEORT zK1=vhIQz%KNWnx8dK*Q!V(SJ>Vs^8bE=<8D)hT!;znO7(9QLIDr3iv_$v03cVIKi( zqo4s~8_D|>Im=B%GwDFtpK;jg1{aO&P7~@<{@#?zlpZmAJ-SpRd7Hc8G#>BxH2KJZ zB)?$ZiBgA8Pjk~=BKbkCx$uw~f~q_zG#zlBAJzTJ1EZq$+`F|`0N z+Ho1H&EY}eOSk)8=e)B_^!;fEJfqR@GJ{oJm#G{09{xBM4jJ8q6uLzH6d=+V^C9_h z!e%|NvY=gW@6x&x{%AwqXjUmQ>>Y<7Kvfz>sC$!d+C)Gw)&SYGv_H=lCRY4^PrVe> zD=c_SL+8&-9K%vwO`&f%KUpLm2iiEbh!Aw6L}!bJuMIG2W>==yjyt2j?LG1itvF;p z&Bp{FeT>k;t}L!mjG}$741u(e+PVx$WS+tOHCxlY!@p0daK@^yikE><;3kk8$-nJ< z5^HU$p8e5FYao~h&L?5Nb@bY%lTs+SF$@5~Tjx$;8vQHD?_lbUV z9B_FF6&3pdVm%cC_uf{BJoz;{)y+IVZ0ToyLM!Ksh?W!fmi~wfYzwkk>4sV(@$g+a~vsz;2Jj zWzzXi5e5)JND6qV%>webU)fUx%D3V?F&I7X^h~cH_o`%yt_??sJ?^e7 zlW+IQi<1Rk^xNHLx}H{f$LYImI8#)Uz1XibXg9}==ACkQ?i!xsrlzZe-Dt!%OnMLY zi>FZx5k8aHdvG6AIqP~^b@xw#y^FZr3DL*ui0AJ$p3l+s`{`H7@F8n9#0d(X+8)Mzdq$D7r8wtLx(1$Heq`sYBtnkPkPb1kX6cUd@0v1J#L>_&(gt z$O~BQguZw@48|mKyWLm@DZxl0CeDBKZB|smSEt!Ho;Q!`j@=er++hwy_Ae(s-sp!p zZ6-KdAl1sZZrvB`J`5?0v4isBBkEn)`YE&roAFRO&gBc4?hI4oo`FPTsu(W8pn`19 z&d3at=7ekb2X&x9H84(hOci;ggLn2gBK*w^=a0)8B_wZP-Nm@pzg7DSgtB?`tOOJ% zx!GlaHcb0mv{$6n>uAGK0swFl;If+-N65FI!Q`>gJsqoeM8gV@JDo`~{r9_w5Rudw zTwhV!p%WbicQ>Nzm`qewR)%6wQ?z8^+ni%-q8KkIGSgwkj&+g0p=Ued;s0=&e&hTp z8p^)J%PEGpaa^|W!*^i9XY4sJyMPDfj`2D4)NJFS91g#Tu4Bf1WQ3M^==@D~p|5@* zg9}h7f<=juzzWpx*m5yll@nwpC7{lJc0HN%_)_L84l9 zgDh?xQPr)m_~vzgvoVUs>FyHH6K0VX5i*S@i?fu@fuE0SSM?jgjmwUH`bbG^H}E^VX5HK$y@ydoZNzoa*n*|ClF;1R z9@573IQaSW-2rlQast%^sh(P1?1Q)Mz7Yw)h$7PGo@yL5Uuj8oKET0zV!|{XWT0z$ z+j%tY_${C&R_=Qg5_FWL1kFOb2y9`g|AwT|Sc{&SBlw*KOh#2UJMv8!%UR_B{efd5uX**+SQ+#H_;lW;rb= z>#kpO;?sQqe`g(!m^O^mNL0QmF>Pdva@D1ttIyep3 zBL{UQSPMFv9a2^sC`Rlrl=M()s?=g@;t0;H5b2|v*AdXBOA%($+llJuKW%{reJme_ zUB3sc>x^?msS-uyGs%dEoFm}ce(~0_2reZH`t(PC%8{@pwN)8Fhvah$aO&@=kuNr$ z+hr535JG6miIvB!k-ge(M4#gZ3F=Dm*Ot@Wka5OoFDU^*$L`MW)kHm?mX~g=_ird{ zg?fzKyYxgkp!P>$Ym;oB{(K3l?;Cu)=eFurVt&d0;xoK(VnQ+gKS{|^BGJehn%v_J zceRGzepuI{bucInPo5$Pn6pEg?pJn>4fJQrK>u*^3outsl|EwPMUA;L(Q#;d6Sra! zf4KfNkeD?-(65hoItILJHQb&vXyjVfwe?cT@Y@L)Z-LCAU#)w~QIWWCmHL#E*)1Z! zaj3;lFeY6xmhqu%JG@*7GSXfG#c?~2Sfv;mHXYN(^g`HWj*?pS1q&)$9>~s$bHWEV zEK~EJ2&^S{d!(7#D@O@$SH)K-9!$rg!n=ThDBY=${oS= zIULjK`X1G9`5V=>!sg&mzV(>r*ATnq&YnPbjl=4c+eW^DWO>f`?4p;OUW6 zK4TS+JL)HTY+q2G{a!BlM;*bqY1% z>ZSrn@Z+kt-Wpy)R`6Tp;KaV{W?fQ^3EYSAy4qMGk3%l$2b+}T>%xfWzX=Z+0%fD2 zQpam`os=*e^yb1z(3W<@-pr{9a)=H>GwadKJy9(izvR@cbVE_yH-<494H}G{+2^8x576q!`)Gk zkzw@VS>Ap4<2_RC&~}5M?qOB5Bh8Rs5AK_(v<@fw%x~(Zt2QYqb#H5;{4bT^l3m_^ zgqY4aHrGD5VDr41USX+isaub?#v;3)8Jqc>nYnB&as389<|Kk&FrM+F1@5a;x0rNc z{h015$EPA*-ws3Gi9j=3&0QBe`$lsRW(}$d;%pDk;0=~?I>^1ahaKt9)<0vu2WL?QH3FH59Gk9$6YxRjvAT(fO5``-NwPZ`cngG33&tr6=UzF}z$S(GNy7^~ zeF{=ynq;a|&FACO+p3sNbY0WAM#Y0xyOpBMmo(5=hCr7-_fAnmt)ZcE=yb-Xd)npA zk*Ymp@E*tPuN4cA_Y61N`*p`fqs~n>Z%X49g9!H$Z*rUp0=Q2hJy$s@-mav78-7t$ zK;?gM=zr$Yf1^@QWy2v zf6_Z8>4^xs;9EyE0_}#-LBHYGIvKF73!6WF=qc8L{D$z^`q(QxHo%Xn6jXk^>|!8< zzA~AgHq|+l?Wml|?vwk_Gn)7KjopQv!EQ^RdvmRY#z7t|ezfSneCRn<@mKubtv>8+ z2SkNuJ>gMjq;GLKuMI1$%U-MLW#@|OF3`NK^W}DA>}ag~mXQ!Ljgr450bgr91-5E| z>AN3Rn@+T7ypOIFx1v;k)A!iYJ$MJ~H0;kF`9>44|I~YbYIo%mx_irMA?7%UwMGug zQIYmw-HIOnq)5LoDonzd8VOM-Cy%;lkjIFU+F#R7XVmsjIM``4xEcV1iTNKpMQo&FKNyMeq3%bx2Ls`t$f-;+Lkpw8E52n@i0ejb22Rc8D=(G~7#-vm zaH@z}oJ!kSRQqahKV{cnr+h$XMK<@=Kt68>NG}X(Phd{hLlZ{!O~(B-ovW*%4>UgC z8L1%Ztf1?91#C)cTIOnbTCtSZjUiJrwx0yl?`B(#9dBfpAL+>Q+)Uga`*qFY%4W5z zt-=y|o@96@LiZoUF?JOqM(0#lNG!k_j?F(ggmkpXz9gW`w4uFam|UjoN2z%t6LHTt zBEa5q?utMMy>}IpaDGp>Q&wZkVNy1V9-vq<*$^Wql@x|$E>>wE35NKQC;wVM zSTTO|u~U2-Jxx$pfm(dQRNV3Cqd=F#clM_~UH!NZiofM8dC)>5z4c7P28~YNQUQpl zJIjd6z;J01DP+U+ONVC#=h*y6>f6qy74SHi0h^|~(?YNH;Z*#fR50uJ@`fD4yemPG zdboh|^sOW40AY;L@7Y2#SW><5@ue1JM0EJho^r7IEy_vXDtb(Wj!Q0O*% z&c9x7&*YC%^*y9A^I-n$3pqBYo8X$?Rp(j8Re$~Y z|4ziNd3q@u`RgEMp{^P$a9hb=S6EmhMO2+YzD*O7NP^( z(34sS{zBb&=;R!O9H zu=g;R5#!imYbgHae_jCcv9svZfXtY>XSVi)xz?t2kG=fcEQLu1Ms6qKGM~OY<*|y7 z9ry!;4|^G>m!-fbI(pjVs-b>rNeqE`qh{(z#%A4SDFg&|r*eP@w5Se^6l=Znt13YHj&?kXpIiK6(=DH9cGxg%g6lCM)CP9TgG~}kj%iL z+8|e{OS{1zmI*Q}?7OPQx67vz8^DhVY1PPxAFARm5d zLBCu6p{Y^S1O}>bEHF8-jJ)*B#mpV&FM6ZHPlSX=-pIMWB^YF0-^EfJOs^QbO76mdEe{5Meeg~gtzNHc4s>RJQNo1 z?et@9JOzm?o}ZKR$w9+ddsm0wKRXC^jZEMX>$n4B`pG&p%C?Tq`_UZ%BYRMC55s*y z<*>*v9sn0*z`k@oRupavQ4W51mw7!8Ffmb(!@^3tU3weJDn?;;~n|V zZ4gjSsx=ze5{)SS2E@f|RqSOHrV#l=MdC%?{m`2`%?FNewt9IyR=Cl55pfY6wJmWPA2A;^s$XafDc~vo zvyELvsiEaRz-$;Rt6683P4=?W)#?Inw*P#{AJ;0eLq8Vm035ZqmYli=>w zIKkcBC3tYx5Zv9}UAl4YpE>8=YwxxGb9?U2)%Wz%7yb16Rn=QnW7MdTbag_$Y}o1h z8Ka#bmHpJDk~T>)w|kWNd5BEPF2R+z-q4rEDxp_Guea3CIs{?2wUfxFgr|@p5tE4+@DtGP;_mL326Sr zIC&{nWeIl9YX6xD8oQUn1H~?@)68qmJZ2Z>uk3p4JJvHlU@}B7XXUJ^R1T*D>l3Wx z)#L>;Vu>RB)IKHFZFo8XuR>6$6}gSW*$I7yM_IoQ4}vdzeN|gPU$2IVJ3P3ifZ~0J zH37{;FmyUe+l3p>GgR&fIb#)b(pLQ%L-=$&Hp-K!%pyA6)@dJFq?>>3R3{XF%t?5) z9spPQ0#$*Rm3w=gE6GU&zAe@KM+o{LWoPxb!4r44QY!Epg0!KP%&2{~*nkjEft`;} z@KA}YQ?g6d9xsW>m7-p{XEVAU6|GHWbE9fVG7@6EoiN8nJc~vHY^SCMlU{D} ztTvLQ`#YXtNq!p`9Ai>0`;`G2JBv_6Mj`kKA3woCT1qzqxtKQFA5n543{uhz*(o)f z`HG206HOs{3Lg6gc~MQF6`=>4Q-0j#H6j!PWt#-^Q#J=|_y~`h$6brG@M<@RwM{FNn_fXg!!H7{IF1%^+MNG{93OhUO#eYtc^iD|L9{E&3egB*uOu~tFS z0T~{{@y@m|aJ=2W_9M#V^Ddt)weQXA%ZUF(b=DGNh(3uqY{8JbFD6`o%r z_9reDB~ESm(}gx7HV~06n(d9^?dX3(<+M(KT6{M{eyNahcZ(-?X1^1$sPb6JX_$LC z!RWj02E8Eyz5euDdasi&`J4P5aU?^bo6c7@$LlY^Vdc5K{*_0+SHD-g>sCRf>}dH# z-35qW&Jyvmvq9y_rz$*2xcf)%0pS{ou#)mHcXZ*x?SW1I#BDjFl;BV{QKAv)33n|# z9v8E?`3hbAeZ5P(e&xg>Jc@|#-^)aW-S^-$H?gBWz-Q@8Ts#{O{pt(revS~d@$om@ zcCMb2msqxVrg|Rx<3e*ioKb8Z-f}}8-(vV50x*& zaTDvp9eigTo$WhyiZpNVcH!w2W}C>?Bt~(H6;t1dh`e^cwMdBf3=J%|^Q}YeinPyl zhWA|!sSLx<5AQYQ4z`LF?6@o2oU@M`b1P^(w=`mHb(&sWl^;;rYn_)2ZLKnVqc-sU zOHY!mFbnbpjf@J`=92jN1D3Vd$JlR0>WN1Xg*;Yy?oU>@xUS1SZr*nw3Odqn=q3FH zop7SQuM~>Yf1LLob~0!(Yk570NiJvfDSSQ;2b%1Kzdox@maqgYy60#k^L>{94o}UD zEP}GC8s`H}pqlBqaNqJxF5EKXQ%@LriO-+H_iHqCK9~KT%9nzT`xc6;&c7S(vUc1! zx>5vpv$cM}dREU>KHT$zu2VyU$sK_Mb}T=Gp{z0eMWf!umf9V)-0|4LOeQ)csOuT- z8q|GL35$G5tR7dC9FY;ro9?dJDw^!;)$7EDt^c^hdUFjSza1LrKs%dA@}C+T~zVHf(RDvjbCfK_A+~9p^J&{)mNJZv+DDob^eh z2*F^wkZc5Nb||hFQy<`C>6w!SP{ik+rY`YezUZVXRj0D`5G`q~>CC3S=9lib(Pm*i z9tUbJDtlPi4b3^au@)-}pfmDDUzUX`>RZ~QswA7{Bld009o?aJV63;N3=mS+oyy~< z(zopvoCdv^nJYL?1$b>|^_XAZ)@?I&NqbUl7r}(!rr@KsF4>!WRGk~qtZ7Zok`pB}A#E1rI&JRtSCjaI8dGT6JT^d@gi|HZg*a9KrZ>$NJFQw24HK zFdRC>mmH?(3)FJ%TbdR(Vc{%&&mnUg1o@MIw5)!FJV>D0S8x=5g;Fab5w^pHr@~t( z?Y35E^;zR++BW_1D3t)~<=Udkt>SF9rlnVWh%)tNQN*}D)lf-Wy(f^8mS7C#*L|)W zVl3N0ugTS4kdJMft3RNO$70$1pxSV>8EtRytu3A6ox|5uR3Na$1>kE$%v~zpU@bfM zko%nK9X%;gN&#iGEi&-PGGVo`?ywLQF+zUFz{K>lvNrOaO1`I`)r4MoVh(4NZsYk< zV2qbi&pge0#w?cG+<63tdd8T)FeoSA{5|5sJf+pGR?yo@u>RTU$ChM&8zJ9Ep4$O* zf2Va{L{3}cer@hLLa)(!R$D^?l}oXgY78^lD$UN;=htE2a((kTSaQVCmeUSCECU0c zh>gf7QyH1t(Q<3wqpIF=FWqsc>cHq3C8gY<;lqYylEu|u@Av9b_Q>bU?*f^=gh&Z2 zmf9aAQ~h=<>>2are(^5=C+&7imY-6G>7a_^;}8SirV6k7ait=6jqOPsht7~@HCn+4 zn=L<)Wq;P2`lfXL=!hzwrV5Wk%A5_m>8Bl>gmM8~hODDP-_rZcmYb7*-3don+^6tTlLHU~0} z%TRCqF^a%Si=}-axI`WEeT}dhri0ja?YCg{s|_QrH{&oC5NRz$8*F31ak_6e#D+JA0F~*S4S?F4R(Wei#IMkXs zsA6YLTsDGhWWd&vg=vI=@|vA=5y~ka<%pZW*_M9lp2&>mB5l}$Ror~|Xlc5R?AXIp zW8sf+YsKV=*)#y^@3EnZ>a&9y7+B%W_Ucs=5GqZ1Yc%!EaO|WRN6)5 z$UI($NRVv*ZZ*anwvV5Pvf5F0Uw-3FO16g|NU%Fp1m)sS2L>q68M*Q)xg7gk541n} zdobL>(Zw&&6k}`Azuwx!4P?>uV#V@BY=WYcO*IIZX@<=FqsOdLr# z@Fu0TRb{D0eC{cY7ayhGV7hvCKQx(B8uj_n>6zyk?)_`f2o6*R)o~5RH&=&RBi0A9 zqVdN~jMRi-(@Yo6)AXU)-#+vNJ`Z2nKF|S|H7^f`!KHP!O3e51P3L-)icRHo?iUwM z5RVwgsJ~;pMK3EAXhQTscW1*Eu;*c}hC*h?1Dcc%SXg zCV1_KT4SunkPRbp9PSgGoDyyJ)44ERgtFG8qlQAn+taqkdTgMMP2#R z!?gdIK5aKuy;%LLDt*gVe(mjq=Tbfg^j=3JDNIzME?k@4U3Di zE4A%O|MnF~JR5G=TQWPqdDym4GjIdC_*HsXTS2kpkl1u0+oGH)J9 zVkO#dZ<)+{`vJQEUSrE&hooKpqqUCy@vTFip}uHidR!TGc5kCs?EeM!=JdP0t6jMFV?;7V45QKR;wuj$@njyTA5cDgusWi$lQe< zc_xR22&A0KT=_iKNMxT+aV9+2K8|f17S0^iXPuL{lp)(_=OB^m3*#dlD*qoU0L2=O2APrrA3S>EUH^w zR_DAz1>-(4Z`5qXoLi3pZNdYwC<|$^Tv;&V{am`<4Px3f4vIMc|*&dgHA!#}kgl>fb9OfOb!< zx8l@fFAiQK@n=M{<4osbdXG7OrqiQ>(eO3VKw{P%;OnjXbYSg40##yZ9dAwy8dUG5 z|I)~Clc0xN%axn#syFo<2@gQ)sG59qQrx;Cz-vELT1xISLulv7@;8S0O?I9me!LFAkMXH^)ne_@JXgiS3g4?*ahsmmLb4gG9;1WpEflRu;biguNW z1?sjikva7@zqC3kvo#xg0hst17+y~5MZXaLYP?oXkaqyR(7qvrqAIhH84dB?HZ-Gw z?8OdFuidA~hmUbN5qkak%|{x&JK zT5`C9(&c%Wd8l1AnP*Y(5CHv}V2eRtL<|IBese9`z-ArtXGTEEj68kA6b;aY*#*1+ zfkNV%(Shwr;M}dB@A-}4Gf*Kfu-2MhJA(Y`K9qn89>gZYdOO%We0>YF6fD_a#LXoj z9`sMlKm!gtQak}y`@yFOE3J8#FmMv$a0}!@P(UuBUeo@N1^+WpL}$m1e@RSdQNswG z%$zPy6_E*E##>--O7e9xD*j{1>RtsQ{X^tOsD!_&mqu)u=G%3zIj=p)^Wd3DSFytU z8Rpxbr~$vKxvHBv06P^9pxyjiV_`Vnb?yKvZeGYm@wqDa;ff&EhH0 zaSHaW6hDx#<@^c_z@5nsnbecDiL!b4>44YKFT~4%eA$0LgyCIOClvGMW;r=etp)qU zwakhxwUI+p+3YJnf&pVVA05+|J_o#WFMqJ)XTjqb`n^p^G=uGJRn2Tp7C)i7$VFaj z$$uClJ?aN~0|&cTg!U7)+M@e<7|pgxZB>W$;pGUy>))5R>L|B?7~kzm;v3If9i$&# znD$c$d>g7)y)o=)ScH1?^v6wKo3sOw(piwLRFOH>;6(Hb;1s_}Sx)AEZd$Sdq15*l zoPSA8-@Ba_z-?XwufPs(JTDS?VBO?-dBOpdc<#Ef_G+CDp1Z%a z-lRY5fg#xer#(jn%e8`$GZl9&k>#P4PjF zG9vg@XLJ;l&M%`yZL;^$#U$$s*hU0nK(aj z4oU}y!wJ{=EB`;7?UQJHC)v6`p7r)~}*$NB!5dVbkM5(og*cZ1iw+8WsoD?QQ@+nD2 z#T|Jq>OHuekioaDxWvy+1yT-O16|iiuWYybc7Y9E){$*k&^kHee1mFNtrv+_+YXW@ zuQ5VO{b#=M>+i_=bPlUei*mSwgS)bb>Tj){cf5W;Rn#TsoHwEPA_5!F%7&uCZSLw; z{$yVju9}9(n`dxq6MQj^!Tv}O)xE%-+b28{nCc}kg)a$^mrE>Kxc?FI!os2#aHTx{ zOd2D9m^xah_F=d=t@|m^?is7fFSTksHiz3q?X8n$jK?NudxQHMdO{-w%l#HMN3X_R zP=BGDCAgrHcXuzSO~k-x%R=%G@%uZ#%kzM6$Q>!ubFPjl{<}$8JaC&gKU@NIEvHM% zC)s0!he_fxZD5K8f3ESru^!R=&+z;?LP;5*#I29~sdoCMu<2M8KF76$kzsX(&kfDj zHPUy!{&9TTAXb-<-{6_mq%n<)kL zkHt}v)SYZGBOVa4?eo7E+D6Np8dDt?Ww7r1jZ-KYs1Bv8`4Tx5up9dhkRnH*w|3Z4jC_U zrf&K-EdBlw*cjDe_Ij``rL?FkQ0Kjzzen?AYyenaQk-3f-G;R;#wKI*nA69I1GvPh zw~GKgsPwY43I3(twje=huVE7SR!>aWna;s#9g>V| zb|`s-9H_87{MU5M->3BVW0DGi1iOB*ZK>?{?~}zi?pYAKVlrNa1b^5$W%g&L<_!B; z=f)at-b({8=2vR`^hLP$f1U?yf!*PAn##wKI;2cb`zg4Ge!Gzyh7K0c{y#_j26VYh zx$H!E)8#@SiMG4Cg4Qj>+mdUiBDtlhmJcVa6>d)(%LOdMs+BigNj0_!16WvF@Jmn3 zdlKTrK8GE_qXT&M=ob0zS*k%-0qi>8swK;ZB#~G?*rfZ*!5ZH_;IvNWN%s#);bC#d zHe_9Dpa7YrQ$FKAQEn{%jEpaJmCT$S%pg+kR!EaT?$zWjq%|&?cd6CBS^y`001`NJ zql4ocw*p|qZ4Hpn<@62pi&a#upZ&GIMZm{4zSiF>e>Jz+1tLoShl>gdzpF9mt;2P& zMzz2E$V3DiVfO30!%sU(=EgoD@g)9cMs(D^T2)diV(Ka}-l5&A!d5p~zfW?sk&2vC z$ehccvP(kr!}tn9dOVavr1<^1+qt?dgybyg8CQBI%tO4Fjo)-zYx-h4LHszgsG>t<@xt1YWUL4sN)l+5J(hx_TXv zLR4$_)6i-`FXoLoOOB2;BIxXIIxlQkz>4kD%OkbA&gcO8v38@>eDgsJA}3;0t=hFz zGZFgoJ;6@u^dQlU_2v1%-H3kiTi!Gua;;#E?XDQCM8k*@IKQT_UW5By_oBOZ^H<^Pj|wC`R+WHW}2u8`r79%*xNLaBU8Qg1#Mpk}o5 z!;#dA{+d;VIu}~P>NH~PrP!7FgDru@Pn{|aua*YEiWJnCa zk^w2>f-cZ=(LQo{r0JR}^+j59e**p0$h^6E) zlhwL$i02VH65N}V)L(el-w&4-`4vK~>QQ@Eu zz-HO7W~q%C-&RyM?yhh?Gz(8=jlNvsbZqb7s`X7-$4~5b5QQ6W><0AK-qe@5++VEv z8>~Mn_oUqX7w6>fN23!A9C@U8so%7b-uy3{_a?pP{d2(y{gP#sU;Na1hvOv>Wc0IA z*$Cfgc%b?doIe^=c7qXe-k3c?bkaz``6ci!sA4EVMi6t~kIwQeH5WAGi%$Dg$P(0y zU)Zg%$c%g&YfD&bp*}O9KP08rp6KQ#`iTOw*?W#gR3Hm^P{ArdV(m#uDos%SEpsj= ze&+3&e`qxAlGhkll>c)axU`b~EmiC=4nK+W>T`M7ewzIAX2j63p(#B6B5?#RmF27r zn)MU=MmOq<@^n~9o!Z)di*ravFp~x}(^1t>hV@~1P=qyp!j-shV$oO0Ba5SGWrPrZ zt+?KU2&F|O8XtO<-i&17PgsehiU(!b9Yu8wik))*Z9xCbXTAJ*S0G}7h>JJr)*IiO z7ey7tRUL*q{dk7r#c#vu-A&<=3w);G`_wML<75S}Bm1CHo6E?T53JA`YIP?sNbu5j zB8v3yQPj3MJRb0e2(v@zv$3P#3u8YJ73Eq*s#%_HHVMvm;>5iogt~_n{F*86^Ekm(XWmWvpURj(#1iQ6l_k{oLr7dcYr_ zMYAg|+SY)DspBm7&gGd6sEVrly&9!Vy413|x~Q#Oy{LwwfdIinkF>qtWB&`v%U1IlMU!pv^dBxx>xo>Y(6i}(`4>bj-BK-k4B`@}F~n6OA*yKu9KuM= zP4oVX%pWy`juIF;G^2niZyGOroSI1TpmI>BHWG^4@QovDb4R2bFwVPMhB!;xq6Jcy z<2>qK7}aLZ-*Ek`$26w~KC}INSXmBShXo4sg$ZSh-5-)0%Mn!sGJ!W>10;AHYtLS5 z%#BBtKLq)3@a{^7_3_I%gxS8%grhLGD#er0Ubg_F3U0a#$Nw`@$N2pjML=a)j&A1O zR)9W?yb($hKLT?e=xyx9V^`jaFT8J!D=S^P-X$i0Ue1clgx;^D5E%NHY^X z!n>D~7Uv}ZD0h3blaX(XDv%duQ}D&LfVng6a3})H0$Of38L343*8B|)6Va)YAuRk! z4aNnnRkY+!mi0V~q-dH^Ssycyxwn1glo+8J7h@HW>$%gUBq?NQHJWq~fHY3{c!sZG znBcI}KL zVMlXoP$(S2XNSv2xLXH=7MFK))C{k|DFu`pV%`Ak5JK1 z48SVT?mew3=_|(;B5SQ@PKvX{7;qc7vO!oOnm`w${z6X+D3?L`kT=uv+cf>yDX?S! zD0V*wZVjj|X!2-AvMkOyPFh}nP4MTJiOPwGqTxNFXHBQoeT&rGW4__dfJeZ=J7-}~ z2i@V3&{Jw5@oVi5Nd9&djenaAG%7pBRLw+H^f(=D*@cMj*R%3mlVT$kG^`93&i5xv zGf}Bo>=7u|L>NdFEdKHZC!mKD%KG0Hx3w!;nz6ryxO13>AXT49q{`r3nnIJpSLl3_54QDnBVg+D}>VF5QL_RFCC!+rd+6?2U(A&Qj~V ze9J1^BDAZ-UjaUoG*^9`fst74j1z)7FnaocIgh&Re-F1|MJ6$TLs8w1>p=FIk#1_~B@4&L|4G`DO*z3j z9wPRq`!RoR!sLyKaaFv*;tHWg>-OKX~q98#$pk! zVWC{N%kv~E!Xg(HW5(Lm-fJ($VMihDjQt*FE z2Um)z400tKZ$R!fct{}BOiy`;oT!RTk7LOp!(a|>4$j9DLF$igIucODPq?Xr?dJ7> zbddny>OG3!4zt|y6+*F!VoB&n^Y60Lw&f1B6Lv3yW@`5-9Ei_r#C363RQpQx3!D?u zBr<~CLnA|hjtnyEZR%7CoD3=)cq@r4K|gg8rWOcNipMvnjzt7qa^%vdH7`deE?aNN z4y{@Q!XX?&EsN1aWB&W!d9uLP9Q@3rX~V=fu8}oKIGong97|lI)Qolx%#rx@=DBF0 z4t&ptR(mu{_Z#00(tj3H&P4r3{?R(bN9?nmbs-rW$Gin0WXBZegaT*pNYLrf(u1ZaICT&jc*aN|`Oi$# zzpng8nD?;ZkyH$9fW}a^ZH3-&zqcJ*3=yg;ygTE(M}?VUi2UV@+26>wHunW=DUu#X zBQC|VqTq#pcBw)6BsM?_WgL22$wC)rHRB+TB)h`(TcH#5X<5~lM60OE^6*Gg-psI@ z!2mwucZhha3O2bQ1Nva0r>Hr5=G;HnN%`Y)p7)YnhaU%2=CAK4-Z-Ub8wvCrG~9}b z%mvo*xma}#rL`d#%apm;47a3QVPIisu$~*CQwE@feZ0oz(`$JoZE}TNaQmY?0Uc- z^|&uC!`!J5YYY@dLkV(Mp61C&M`SHKtc(57w%pBm>6&z^>T- z?f;m1g4rn7IVTR#*_0A>eVFfjz-s(_(fRv*TBu35EaB6VEu~OH{Dh`D6|w5!Cm`C? z!lOQ@<)6k8K^DHy9&}sddD)?#*jVQZ6h$su_ye8?-x<8*nehDIjOBmXQKK0QCXNTL zIZJ+Dr+j2-x1^Yb6gw-wGF(AlPh;LULYuYgzq&{LSaab?MLimc;W>K=M;1n)c@&Te z3a#N!PIS^yUnF@AZ62d?Px4OAuE?w%l`&*=6e4=Pw*}pGEIJ+WS5oaOzF?Y+qWNS@ zZjBPI12p+QXRdoyfR|;DMvj4!G8hl*#;q#mq*-BWz1!bEf+LIt*c|Y)g(GyRZv{cg z^>bH@)wkcbt=w6pRc;~nEZ^5KHSsXCdAs?TL#0E@)8OFXO(JkOk*4ABjB^mt8}g4L zpiW2?0qfh$hP}CzEJp#8K_d)Gc^?U>6`GVeZwayjNQ@NkP+_m2Ks6WhEIgbY?2N{JhRTR~x4rC%2B%-qj7eI!ZP1sE zEzi~&wGx1J@`tRazobc+i9;#4L&FReu;q{`#daO-mr+A=#1H?im$su&83UPC+8B1p zOEfEUx`$U(Q;<35#X9XvCSpk-(ve}#i{us-QYPzp*Y@{2Vui_1Ix2pDRuAByOr+pq zjCxf+9M5S^%Hw9qw{Imh%jtTYc_FkO6`p1^IVyk5Rc@6F9jLYoxA>(0!>#xCmr4*6 z1wzoi-0#+89p^TL76{d8mGa!rX(sCTXt$x+ZR6*QLrgGm*Ele_ zai`|mFBt3!;F=4F$@t0s?;Y%a4%L^8`D2ey5Xy@W6R@w>{>ad|(lyu&c_6gY%o9S( zl;iIhzTk-^=`WR&>K-Ik$Wro*@PJFj?-&17u^sK<=b)YND-77nxX37OLKHBL{qPU^ z^?!I_zcm7XBW@x>$~w^>)b-6W2;h2t(oITthQxWUWuw(D@N6vUtL%$UEfshf^xgHA z<$MJyB|lgVt~MXO(N{o5yPH2d<>l*0r3Bun_eEry!=8kq+s%5q$~8wyg~tEZE&Qk1 z6ZSip@Bz&q_N)h$ByH8Kq1+k4fJBhM5+f%qcs@oR!uo&w)i*)8 z348ZsHV|##&%q54Cvi1*&q+u46`kQ~v~rvp(ptn+D|%X!A#>ju$-HIEsvq}s#*Gc} z=d<(c3?;_9N2R`kLj;{Vr33D!;NAm~%)n>E-C|Nj2>_$RPH0P~VNeFM?+-629HKGG zv(V+M*ceg3Ih!?1*|1N^6>Ua9tfboYjrzkgYIGC|@0wcso9@`nL*hTnE+&9BRmn1QW zYA?zc`oWtsgBC_0(Itw^lS~)85waTfo(uNJg-r>@lP4|yQ|Qe=T!^O*JePQ9q#R#f z%--1G^@G5m@P*6U8{dX*Nc-Qtn1A_bBNvQqx1T%u5XPodK*?$tAyvkW!Bqg&usb%) zjWbm??m)Td#%>mda{q@jtu-ifhyd$ZrAT(LZZX?rD8~9mu8+vt1MZxeD3gutu|(fi zEE0X`f$eS%xj~Swoo}iJ zT1IFCFGeS&E-0Whg(t-^AE{e$$CqX-9&7uZj-~QNZq^G`DSj+P0j0h#3L&^Oyba2WX_>xhN;3_`K9y@B-X+Q zV!99#Mb4rJ#y`{S9*`-DuTNWWVor}2*{TJ0A?EI|@A86ZgYbpG^QG%0xhS(BGGp6Y zW}?v1D3n`=D?=La&7dR+n?ui$v+?v$k9+~zVBB%k$6F`z-Jq$k_*ZYJH1AP^FXsv% z;4k5Ur`Q&rK6bxn$6b%Q0Nz$ET26T4lK&{=|I6*G1xW3Y;d&AzUn#t?9kXu5$rY5k z^xT~1fU+W&0$t@7FY9hPI4>8O;|V9+N0f*L(ya(-kLwYubfDcgkN;lB2vmovs=p@z zZw=~@jRm7GFTSm$#?M$l*%@uI4WN1=G$`~a_~E^FAAd16=>MMU^X=yvx(E zll7AOg0ia0eg-Z&;R>$IXmP%4@NKOAQ6C8$%7{mSp%duHgvsJ9>Q)D#roI;dWgjvW zvd;a>IS8VDt4T#JnU`YzWnng0@Ab2Ok6Z)6_iLnZ4qa{k ztzsdtKvu944X3lYnMa?Rotm!3)XDLbd2cGD;JoD6R|S-*Idu{ho_C$_^3K>caT0j| z{fJ&oO2@z56SYmoN8`nk3a-B}=fxKCTAr1wV92OMQPem!AZ30ycb=7MS$ zJ7J0g;>`mFcVb(fJaNDmaYDhPvL(SXt7rmP4`{pkZDpcEHVn?Mn=IZ~4^CxE-z70O zDwYYfv!Er;XKhC;@G}p*1JfFbVeSK7wTO`~YD}2>x=>A)Z&4R#C%_4~$K5@jQceUR z@%PN&xj|hF;xFe((NLg(br4M=WGTV|!~B!2L68?e*me*j_QAUY??HRQKmy1F-idQ$ z@Zwiecq8>i{FYTFx)@_>*o|K1F(--`y{cUx>%w`nZAi0-S`)|DCAJ?|+f4<#L_0o8z+4m{~LxcPueDT2u#zn>C5W)wO!3`0gb__1A z)DW4~b`n!bjZdOk(2%0-(sF*PP-j`S)?r?_Qm&7docePtYTG=+-h*vq7X~Z>b0^Ph z&29M$>OLB?V;eK)^%S6^`K+^n5B_P@J$}K&>}*{KEbo=a>XAr(zdLOk1mPin)rad8 z2*Je&&>CJB5`)YuL_7HAj!X$mMt4Thv~<_~Dx&Ikc&+a8(INWsDDW8y5}f_$`P@p% zQ)gr)`At=5M7FIH`sOb0t2f1cSHS4lkMrvRlEBfGHa@PrF_HwT4|z__@$WAYIlKG& zLz%OAFn}fRJdK5s&w|=%-i))vtQjc;Y08s1bv^XiVmBhT z0Mtp3%2J*M1U0flT>kCvA!Jy=#qlIl$ND@%>Satxhz z)p159%`aIVJ0M3q18k)lO)ZiWYMdWCg7eD-i=U`f+4@@=`&V=~- zh+bCATgG$}2h10MPHT(Q)omsr3h)wSbqjejr>0)wKjobElh0PWjj66bW){d|L|#Qt zFYO{yZRiBA*!I%LFs(Sb3IwTS0F4wdjlE_=xskr06NLa0W=*45cM?{k>;rON*fav@ zL{EBjmz)4XDkLYR18JxLdDDLU5Hca82jktN!3~N1ySu75;)rWBYHv1hKui#L_Ah`$ z0PWlALuAfCPT4$=8)Yno+^HjP)zY~Cx13Oo zVM6p3Nnd$)^TS9(1ZQ(2$QUTv|Dll~y*ci8`Brn0D^>Kvfr9a>kghhbZt)$`%?eBP z`&yJ=AykI8uZ&_I`CsR$JPi=ukQPiq0X3>4(3eeZ+%2372-$$a8x+E3^MVi=62V1! zb!GJW32eNpAJ%RliM(!5u-;MaHTQHO*}_nSy8_>uSyffSU0iPGX+6KQCv9DH2LC=A z0qi;6727p@Voe;BX|W>m%}G!;`+4B*n|FZ^8t&Uj`0yo@t;?ekyOf!a=UPpiOLi)~ zD6*b`nD;O)|<6R|^o;iSLU5LAiFF9DL#<>ZX>+3v;>ILjt7QtdR zlHl-0r~fkt0aw9=ShT|n_Du|F7hGD?&qz^I4;07C%b4UiO_WD1;Zs^*J~4mQ*>S`^ z2y5aiw zdtsx{>K(^svxZ=AL?$T2D0d>es@w^~V~k5ZPs2rP$=yeu9BtcF2 z?FLp5Q|#vRJmx~`}(V5jn;sADN%Xpo&`oN zBQNIEMPr3IXODQ7rhf?=4FKNvr=yTK32g5Fd7OTSYb#6%c{8dUr)<0Y-G1{5u8=%I zwe2vz2loc(9nyI_@$;nD551EQ;rgFMCk-o7P^P*B!Pd-^=VvEIr&AM)LSBG#%jk5w z1DT1P7G+TIux%CXf_d~_X4w^O@tW?Ghx*2jS7H?x?XiR1e|cp0K;Q7Z-7d%ZHd$f{ z=|C7QPSg!7zHTTkb&N!`m@bO&E5@$H+X+kNdc%OP;#wxFJ4nL}_|JnoQ6JQHJ7Pl~ z2}=_?ZRFsdV#C~2KllB&7C=hP^Z^dJ5K>DvjCo~($9GY=7CLDN4>FuRsa21G<2ElIkt(UoW1h$+yI#`z$? zhyCEZi`Rp@q}k4S@PvUI%(umMl-6hi;~Mld0VaLM-Ba1UaDSqp4tYY3XXqr*jUbbk z3_9UhI7V=S zZbwc?3~lt^{6u=u2fh6fj!wE~pMe9ydi}0?*H(#iQ_#+(* zu`QZ!nA`yo@m9}DN4UK2hb1J%jFCj^Cs2-DJZ@b_D=jPlw7kQ7y(+NaQnu z;~BIf>w1LTDJO-48S8acL)^!47)a@0LhEj4IH!xF$em0^_`u_J@GsnnkROxkuXxK` z-oDw!DDo;I(yRA$>pBEKaPdv4_{|bgFAR z;F;!plQhgLvi`sw_xxM}-SSEZ-W)R425AYg|7e4oy!ZkBP1Rr=F84Dsk9DoC|Fht3 z9u!d}jqbq#`GTWz7u8L4}D72kodfzL#-j{}TpgBGYN$y#o=#!9B_7}-VBQ>b1txnPIjdEi`RNn0A zi0`<|uZK@CV5eBBPu)#48|7}IU+-u2&>`o&H%|(?w))u!9Yq;LUpXVioweru$x!Rx}&2Gkp&R|=~8Vai(U_q$bWB?T)vRa8e!z846c*t zYk`F7aAx2pP1}$kn`**#^1BdYLt5v~p3=rtxhqsX zx|SYB5=U1)<_~Y|A;wt*SebMHhypo%4;Jr;cJ+_cPb_(T{Cq?(mD-&|QyDby)2hZrNgn_(DC_P@*s0(r^ zaB@y!cRcO&=OFKAPwL_ENj(+bF2ft9=4>Zr{fnE)k$5WZI8OM#Z0*uh+joH5&VBmc zo;!JuGvYVHW#&d*c{$pp7?nb0^E?>7>+ACS-uuc3|!WAcfUxRxS%N8T>g zQPK(U+2C3j)x#Dd@Lb}RhgC85f#?%(e94S_;fRwxzpL#t1bz3BYbR10*9ERYH%By? zwM%ke0`Wvxi1@~0(50*fNY2asbx_foJ2=*)1@{4AcT7IxN^6?a7ybwZx*19OS-;mz zq2lCvz`PgN)4P(xuIdpJb*Cx58?3a^tzSzZvNC%y$CsD6!S}lEKS&c6YbiC_SH}arbW}_) zln-CLe|a>m8ZV@P$_V{E9=k)h?RF9#H473=DL7R+AkIuVjp4^6)=a!#`R&4Yf3#wJ zb1leEv@1T?J>h=uG*~6NzwCK;OcK*v>>l>O<$LZdt<$AnBXR(|FTN=H>;dwL3v=`MH`f3i}m#*e4Kcz z6w#gzAs>%H51^)f;NUFY3;h6@8tL~EF*Uz&?DkyiNcNUj( z29b8T-nl1RIG3X8LS15?upu9KC>Q8wyP^nr*Zn=R6vW6M@5M9p-ST}h66XrKD3ux&);g z=?+0cO6hK-yK@L>q>&csZjsKRyK6}4hJj&-GpNsVzQ6VR{$HIp=d5+snl)?0VYBz0 z*M05#v-h2Tp<)TY=EG=lTV{z#NmWi1O00gg;L}t~g3T0Yt(b0FJoi=2_d~fayBM?I zu-@6htFE4xu>^>e-|RiRW~rx2yX@z_TbQtPYE{43vNnnK!6p%!NX?#}JAvX_GBIRH zM&G+hqYU^k-jFbQet0O_EAVEu286awgLa3B&VkD;ZG&FaSF%gb!`M&HM-x!3>5*2@ zq)wOj-mr$$oCnH+&!`ARG)_+hS~qaL-#|hWheAq~3j(v+5ZR+I6G8B-er?=uhC^0w ztevl-^`G5R2NkJX@GT7B6Nv_{*@s<>&i{cCP9oCvq4;jC;T zw?`MIxa$}WN^S)WBD$xy2$x6i23QyG@JG#+j^8iU8nnx6>+kK|W=jbh@KCZ zWDMlxUOIk1C)31Gg_dIHYSQ2j-P8qd$-Ug%b8#ff_H#G~Hk8(WpOCCEih4utTDZM-V-{mO5Y!WoHGk2D1PbDPEz8e>3v>_edw-CCjm9SYE?IH@^kV` z+14>?mw#ZDB!^KIyewxYZf*_X0knwU{a}s!iEVfXUVVcUZ>2x#9r|EaQ`;P+-~_*1 zFujK=Eqpu+3V>ZyAA{A%l4SiFAuXsqDt6s1Vt+9*YO4LszS!pVJ;%t=`+`8+J9QT~ z>~UYYS6!e=taqyI7p#3el%Xv@b)MYx^tBjC#qux=BfFJ(N$gcAi%F!Pi|pqBwoxKO zVb%I_sQj{sQJ3l+)J+YyY2a=gJ4?~|IgZ_{FEjF{=F4%h#X+uq1&kKP z`gShVN2>+S=+Q(YFW2{^;`D;~mzyD_Ab34?4QFup>cAfU)ouK7jq6`jM3xdGF z?e}#>m%{Ts?Cd8Lnqxx=Yc)pX@OtW z`f|;BnT@^jVWKM>`vIH9TnZQR5S7<(FKT({RY`fnO5xm7_lt?e!9+sU7Uhn1haBi- zs-F0JucC7$pgjq`p|Q^vk&v}dPW5Rs`5lc7S~K?*v_^Vsdq4DqvwsmAA%1nw$i&yY zO(1A7*Xv}tltIycgTeQM!~7JoD>G1EuM59_RrQn7qzdHM=~%boA$ z2DT7Et3{PRdfPHPCyc=F!eMVB`sTv|4q10-vwmsHCu^1p*q1%^j+T}i0}h!_H7BO> z`U0pOc1N5h83h>L*pNpA#K`OrDp|7G4j@_qmt#5N!NNASiu)D zDb58CSuN*q2_?nn52+re!6vUmx&m6RHIy0Zx3Klfgm_Iz}^vO92j zVjXc5o^8~ji{TpnxjPX zTp$bTxP9;Ibkss2C02VkcQ!C|-!0$tkW9Y~5`fx;aG4GODJcNv4w&_pL$!Q_5MJ&F z-JYpuPWJ){%2!>=mS%$+m3W;utmXrW`0#jz^8OEMib3g!9%P#|eY`Sdc>sekIZD=0 zHvPd}B|>0!57jc4BkuwQ3Ll2bu4gv2%Vl@L=HR#7cUav&1z)@PrVzFbG4TC7S8gTc zAUad)3#A&BD9(cxe<;P-YWCMr%Tt)rCWnWbCW4vr&j&E(CMj>Lw|v0}a83A=TXazb zDj?~5ReUY7RQmlsWNza-$Pnc?IR~R+B#XLiB(Ys^q(9s0L`E%QlrgBN&*^@1jHIZ* z!+EyX%$X3Me|bS{n?#^+Q0=ty@Rzk%qIVufv-KpfS|HV1@q90;Ei1BdUBmCjlwcXbEqr8nawW*{f+*`{NitoIP7fn;KmxauMNGX*1LUv+2S7cpJ&O{&CoqJju~==?*vv!-Gx3dmr3<- z^gZuei%q$1=@LcSR@mNhMV-_^t*E;eazNay!L9e8+GNFjjr=*~y1^%6>sDHE2-2h^ z6@4opLhi^)ldn!)qd(UO>yKwdc&U`wjy|RINvJF+Ai+~cu~SCYZIau$&=qSkwcK-k z@>M~74ce^LT`HU4+sbP_uf*)Z(0d|CX|YL~tSb8yYPxNCuU zCc%!mn3=d1B`4)1RTtIryX~A)0&x^Qc^!>Br{*2g%W4K2BL{G2Isua3XMDp!z6u7$ z!6Vxa>}?>%*Q5OD9Z}j$U?|W@@xCKhyP$~H!CWg{54P0_wc~X&FM5hk&i>_4p>j!)q7v_LsZf($Uu-UmwVK=3K6laWV<~uu{?21XnOUa8>Ag{G0|EJ|-oerVl%zw@EYP zMZ@k7q%&TKcjAVEFRC`Ljz5kU4kdvO(+3^Suq{&!P%=7Nzu7H!~@2 zW+4K29w!Wkkj7s_>zIuBH$E8D( zUvgX2S2>Rv;F*#4Y5O30q>eNDf+V&PKjB=^U7b#(PTZ%SE+&IQ{Cb~r(li#NVS9yB zzvZ3EFQ8weAQ)<&*o!LzxoTJA-piXJ0mkXD9=`HwDh7wicNud+&Y0%SGH$K!%nnQd zHuFC-Z<+@7A|d@$U2XeoRh98J2VG{jZfA^r!Li9=_gJ0Xf|F&w#*1#0e*Bx9xY76z z86R%kel|GyBHvgNRJg2QLI_YF)Lx6~!5jXZ5x%5Ccu5KK4cKE4R@p{O(cM;^B- zBj0vw#zF`JZlCPY$lShyo98->_{lTTCb3%(xV~N4g61f)nM3J3lk%EM;{CJ=SY2F?@WOOG7&_SH-wb!J%Y)p8Y_lzT1W3o&wTKq# z&!wgf1s13bl+Uc4YN!+`(=Ap2ezq3xz0>nCt?4b&MIbPdPm8$)5BYi|J%n5zf8F(o zmBq&0_uPQlEY;{ZyV4cqS%cU*c~EYO4e(r$SMoiR4aV&kb!LG%E8o7gi-9mQct)7UWI zsdNG-rJsK@(FE!|8U#=B9g?Idl4LeA+KHqP;iNYEZjRylL-gSle?U7Jgk$5rrcJLj z9u_WCyh70;OynsXdB{kKDW5>Ahr20mDB(7um4d0rh5y0cyX5TgvT=w2sT(0 zo=c)AP)cMu|K%$P9(q8ljwkxA4przGP5xFJx1n0)!1@A=RFTQWI#^A6DXV_`Y;~_9 z%rJ$**^u{M``*?9xN*P2au*bM{BHx$4`AcL3X=S zT4IaNS}_WX(OUZ3fh%f78tiobHY_MlXmzr&0PUt7mbNV+n*|osscH*^ILUgOzj0pSXAGSY$%dmn}W0kVTeu86$cOz0yW z?9iKi;DkS*SxfX3cKa$6Lqj9U9e%)A2cwAA{qRt59fWilxvXuv2eLz2NhNAz3_$1J z_F&<^BA^sW2=<9x=S#z;K#WU#N;VhIfj{rWJvyCEJ*q0mlY!-dJ&|~U#kHP3->~2j zuEHiJYPq}iMOaIc1>&;LF%3+?n%lKZ)WLbPc<}%?8=g`#)+zcXfAWvBS={ zdT~a_O761tFFOM zWRv*ewfmA{X!=ChaB(>^0!lk6zvOMxog9w>RAC3$YKP@RYcjxsW4e__l*)a~Qh^C& z9j@)a|8(Ca55ey{2U*K+#IB{Bf((|(VfWVD5{(99W@o`0&NIp6IlURwqMX0@cp%x* zCYgC2WhuxZ654$;+7n)Rh)%-oBSN#6LVV+DIyni9GhTtuVNOgpn{lIUL66#K-ibX{ zxIyIyI^nnoqfyyKcN~)<7YoBRBA?OGL=PUy%7B-mb-fBdSx+vvF@nu+FlqL#>CRXK zO=3W8=6evks0VDLuhBz-GPn1h2#~edv+lN>gXRX~wq`N_$2hj_aqtIOuXDSixYj1+ zzSDKmR+XYJ1CNQ}F*mfKjv3|w`dCY*VV5$wJmxu1K-Z^S%W^?kVf#k!~2MA z^2tm!qC>hUw~838=V*sTcs zwidq9LwU~xTdmJu$#{I*=*<2&CFY7jb-I4i(8r2>c}G_0Ph@N7e%TEg)eVr^>^ThJ zk}qOG^(Iv7HI@dJ3+!H6kV5bIoYQ0Jo0`L`AN5#1@hdNtdtS7MD}fV^7?_9XLlX4x zHWyI)93n?mHM|&WenUx<o_%QPLGN7ve!b8h5#R5f|?4 z8o$&2^hQZ+@t3HY_{+(WdV%pIMg*WZZ2q^pQo5MF35)HgM#SOrw?`3&7JeR-rpyD_ z5g82D50TY#4>9jcy4 z;P#XV&O68wdcK`_p%aE%@pp=xberou{YPaiCZI5cWFHvE4(DXr(AnVD3lViD+&vZU zAedhiGJ=-dj^g;#qyeg&sDs|SsExiqi0@j9m3>r0MF9f-l$5qe{CH|zGQO{A|f3R50Mj2L7VodlMJZ)zCg0?IO0Z&<+jOH#=#dX2|`ck zXS*o-U*Rv*6v8HNtB8mXE9ETZm{AxkKy-aiaPNhlQ*HqP&09vsKo&p%UbQCn>HxWZ~G(#oQ9^Yd!i!pZE@$D_T+)m7HH6*WvO< z%Dx<1MA{j9EPQwXp^Q{M8NEjBnz4qlTd~*syw$Qk@&;VJ8+>V?chr=(ELzCBM<6N1 zTix-H*Qn|iYS0yf*e$v~cUVoNf=YIGF9Tb|7q;|mKU<>bx5R%T-yi4xkN2D4b^$g9 zbx*cAj(mNlvFyLm|4$D1_Z<#FN2((+0`r3oit^Jx$rusg=VdN7cAWh4-~aw=ryK#3 zV`mzub~Stij$AxI+%6slY>i;;FI)b-waNO_W3|620|F8d#OTYwesAUs8JJNCy9Z_RC5b?j3 zGldi2eV4)@CzXwX`{CLDGPD3qe^m*VeQ}1;eQ(Kr&!pyZ`_qk{+mH!sv)3% zz?`A3@c7@W`YehC8}}PBPPqIZH~ZIF9zO(h$chGpcX)2TvHM>O{SA-NKTNHPeR=m^ z7Q?22MyJHC5!3vO`M=2FjSOsDVnI5NKXu{%TnsWJ{1XP`{V%i4QvdTme!~Q0*tp67 z6sgx?|4T)m+YMf2i}n}@oqcWiFD^Mn2^;s*Np>I2e_0G+DNOi;rP6xmFa9IE{FTQ4 zuhc(g{QpR)0lgjk4*$3HW`og&oLKz8o7A-2+%&mPkflQJ2{tBmnfm7o=c9+#OV#(+ zd2(50cba|T|L7~=2p49o0=0iLB+NRpVON_-D6L1u$M7$_c{Aj|!pE1qX=Wg>yye(6 zA?DQgW>crNqwy`z1oKznbE4!Qz^B_UXx=$4j_BGnkU^(%OD4br{|4_VGMItv9mVz= z{QZN3N=S}nlUjNQj*x~%9Y@=dt=^k?s+S$vK@U3yosA9_9M}qqBU_HgWZcf5tulo7 z;ge}ZtQTAFfYKE@&EEhc#s4}Ltd{(r&cZf^R{JxFN`vv#we;&X8vKS3s=AY8#R!L1C}~>nU6bl$ti%(T5v{KQbd)E zo&-MS`6+(Hqup?Py?9NpM_#-mm=u&ww#_%=-9C$By+No!x%LX-vfzx#03lD zq@8Ad-*G6*V&zfoqO9&*9QBXOi9X=bL3h7BV2<~_9D9cJOV1Ms%_-QLVSIlls<;!6 z{ad&Jnbaq%7t}E}&QYN59w3@BV<5Us-$QfDu#Yjg`bk62S>_^&6zc?KPbNU1jl1=i-YiE86Eb4jg8$tU*R!G1d^c!lZ9v%8oZ86 z+U6`aciM{4jJkisnTR$tNs`V0P`3OC)8z-LT=akKvgOs7r~RM?50{@0%eLN^J=wUX zIGr^i<_Lni>VuA7ztEy=cN~613!yi~I}G;HH1`{Gok=)0erJZ6;%WA<%bfm~=W)`D z@%2!@`|jMBQKxeM>S&dTYWEJsZR4KapkJxSp(~>=HP|NtUo_t8mQ9-JO;tOuO_R|p zW&9Sq=oRWvSHF&({T?}I-AV>rYek}@!7A=p83p3D#;4b?1zai`h@B5foWy6{H~gRcEweU3grG0;n{I#nlSb* zpK<(lT=yMYa_z&mvK)nPGj)rG?)c|veEr^Qf`Y@RZp&D5FPfb)1M%sfn*PoUj7SV^ z9Yv+v#+TiTc;5;2Z25iPEN;bZ9v5o+^Y{uzFN&)fZ}t2Np=qG*Cm=pWpIp=6 za}X8I5_t|EHoxOXYWr%wLI=nB#0U&T{p@{ENK3BQs_m0+VCXd1bQ%&{0x`p7d>{9~ z_o=`0iW0{u;g5atQyJLV53FImw-n`fZhu#64q8oMM*`7$0#rkQq9Y7S?6EONl<8?O zxNwqfHSj1lDSx{q3&Oai)Rm(zG!b~tgzDrPGCI}pQ~@No1lLI+c3309Os>wV>YFyv#Z*N~WgeQCeW=Kt%w+_E*JpSf~IA}0Q ztM!64onXHXCLdVZo6GQDS*;a#mUtlu3yPkZMODFm+)%?mzrySu@hU_qjsUOseI_N0WG`Lpw{&>K(VLQt!nD!Sjg9OGo`o!G_#2 z8!-DIxu)96d+6h2V2SVIoFp9+>irOzvrdw(6wVsYtuAwcTmXL^S|r*7Cs+vZkcFYF zU%3^HFqCyc8xWdL#$G}Na`tn*l>DT)egs;WaRr|aQs`wQU8?-j+x{A&ms<^`wp|1Ev3fg6!4a0sBQg)*Q;!^u2&+%Gn zoPC+u16l$mAJ5akMqZ$PqNUre$8b$~YG}JtzWhA@Ykw>BS9pZHNVVULR!QZCC1E+; z7j+ej__)1iudbFibQd%P z1x`Axg5jTV$8e1yHV*L3FX|PKSr?&z)!H+Z1s8= zw;t77NdmvZDd=!h3RGgRD|OtODHG7Mzbyu4(lZ`UE<5e7J~=K89IC}Fsi zTvV0dw`*Y_Xehw4e*TYg$tP+ot7hSoF`nydi*f@{%k5(n3SXtkF)BAhkFy*u_>xyI zjO)7D6V>GA5c$rJ%sAgTjIP%w0;2VrT;)Vk%MBmZpDlt4^NKq$UE?I&F(JWMytUN- zVD2$81dn7KyFof|r)*Hglx%FET8lxCG2^1{(dB0)>uq6#7n4rVU#;!l9{itn1GOs? z)`b~I8-3E=M*DerkUTEubGztke;!^RRP%xBua>PHmM?vm>u2RR;3XL;x9gbn+~o%+ zp3Gj2S>=auT53+*cnS7hJFDB9uM6tvTqZdIw?a3MY;1)05~qJ`%uCKvqTanK3GHxa z8?VEW=QEP612a_kJYu7J_PF3bj{j0A9*O)@rVDB20~@CmY1U!o>AY^%K+?3hFF5YW z6}}@HORI$J67+-y8oy!d8o?9f{pN_aaS6ppPy128ggZ#r$h7HK!X(>hSt35eT!AQ8 zc8k;Cm(6B31%-+fUr86iQ%Uf!3$|a7CO?n!Ap9QXd8(FwcXFMpf0`;Y-3!@`3#RBe z^l7-hS=yn(it_#ydqi1?T-rN`Us!mGNOS%dV`QtLas4o9z<4VtfBvjM@O;Oq&fDkH9&d{A+DnP9x#e) z>U=!@@UH~MVvtt%+o%KtZ6R}&mfQJOHKuQp6kQv^5hnr$P4kI^8MOur^4crub>~bC z?$YayZPBKUudi-WW`C1M$PDnpPN>_Ls=Okt9bs4f>@kUs?f})bH+QGanMt%Smf;cK zw|hX97sS)f-ravnZ(__$j?rs$f3l!wW^+glG1kyD3~) zU>o8`_(huXhW?%7gVR|!{$jOhU6P1re-}o)&RL5E!I3e`3ieZsMjO^!!K2rWx3(RT z8Utv@`p8Jw7*ZLphnWe8l0ux469 zj2qo*9bNpu5Op`1hfywP0ooAiZ%SVdqYW zbbSh2URmWDZS3@Ta%Jf4H%F50`0gkUh4%feieD)z{aR6mEgr1Q(f%=|@z1*akxm8| zBL-wjtuP+^j>rsii0^NvjnE&d7>4)q z-s*8GY3|)regubmK^Db?uXmwH^{5 z20XlAZ8ek3cvZ2yjV-C{tSk|p;kTKQKFaXkgtxxv{gs+Ew0#b$bZ|Vv zj5HQQSM@hQ0*`hF9|xD`oR*`&l2FXn52^09{O6DmaYwGf-vG@Z8*hvx{SmqnK0;Rm zQt4swzsk5t5?wblLFwKO6@g}#VRQq(QurgXs~{n zk`sfvhg0a$3+t@)_>}<{DoO_pmSbU_Z&jy7Ftp+q<==-gxcFgS#9qdCYM($s$g!G{ zX19zSR&J^!f*BsMR>05K**v_{mun=Iz56pU@Lm>g*dL<@?6pIN;2p?M>Zk33D@|8R zi(%AS!<>9v|2_-iRx9v4;B%W{6I=-ad5`L^!Z>x-@|RU@tAD4^Y993quAMhk|id(ux&?Mg4wy%Zs&!KLOM-Zo#7ubb$%ML;MHgy==qg6O-rkwR=#gfayaEc*p?P{k|}6r zRg=`p2`y7G0HvX-H7Cc1fgAlpLi&n_xxDHBf`|FXtUVPTfB1 z%)Lc?NKjfjQ4ikE8az6-xkv6s(#o%m49$EO+)Jvbphc)Ba|YJIC_P_Z`Q<89=etXd zGwM)YmRqHj#QYQG@@ln={8nlhfe8z%F%37UaDn+9Izb(i<(;Q>rNvEYULU7u&TsY- zF&tI|aAjj}`+to@z(?G(|HSc6eiA@B%Hs05t^6VZ!a#Yr)6g}ro2U%c2>FOEaYjAb z*(Y*vv~ylbxPhf=J~Ffw{|uKu1oO^DQNXs!MN|&(Ey5E`fjbWPikuto6>lggT;S>>? z{Ti#NAOv$KGwx7{Pl~82_9Drn`Z0+p1a$;)tU@);Yf(=88oQO*9>1ut!URYN`ph>1 z&tCoTCw+oNRA*TqFK`j~;io>z7mt)j_S)9T@EKUqv!qeaQfXNPC9ck*M*or=fAC0sZFi<2Xm_W7uAz}F8l0qlW!RjhWcoAoVV@TO@^riQzd zx&jc9nL>1N#SftR1iu$cF?6tlkryY! zsgq{I@6P(Q4WJ}!C|>rj0|{V{FF(ZUY(TZr%$LI{`>+HW4z8sPhu0MZaD;z2fXJZS zKXPnka;SLNK`ErGakuamobXPBF>rk}OSl}!9r0o8?ScOCIT*i+!X8oKlpw4)duL}~ zyd^Sm;3C7kNWUnVEr;a1FP+R*^yTpzA86WnJmoFPTk%S$4l<3A=#ti_(x}qLB9~@llkbEx48EceOcL_K@2)GnuXU{VA8_7hNxqX_tG|qqGhatfZt4Nij4hun?H-nhpED174G= z=L~X5f?f}|Y%08 z5y{6DLv2aO^+uSw7gMA(CzdqK&AItGmR`tQcTT&4}Jojcx5tM%Jt-et5o7RPGA^o`AATAaEVbB1f zwIxxbD-Gz1Ij19+N^+uhdS^?j2dyVQjF~I*saib}SGgV=dR6Af^5W9xsH2}VpPQRI z18_-Z-gUj<0MCV4e?D9w@C6io92PYv4Djz(rSp1flj+ddNj%cHj6Y*zu~<*HWIJ)y zYbE&@Wai`$$Y52c+p^m+ge2Wnedh3~H)xf+Gu+PJ(|*1C3oKr3uOd3%AQ#L>5F|8* ze872o1pWeR=(&Ec_B7yzhHZO&QEz!{>|~HdGwVmpienEw>>e@gH;>eOfS5cSj*kWL zir~43L%<1qsc3r*gMb{`N}=-23=S{U2G#6#0w4Why4rcXEW=W%<#!n*dW;K`VYm){ zAzgK0XgaJyvQIEE{)nKf`7vku`Nz|u-%zpm={2;L8ryDwN5}u;GjmGZ@uykZ%8e4$ z{$I&IT6 z_6~%HH<-OhAXyH)hxH!)gR>)?RjC$iCryBoqreO4ecMH}(^=zE6d8EpQ#kf%p~@p< z<)>yFZM@xoJ(_ax^~~7OCMx$FE7+SV8I-x0Xx+*D)XGd`m(^KdGS~BWjtgj|g2A}8 z`sNsO>8?)=#dh}LhbPDdzgoQks-!%&B`_Xk=6s~9dkYK;M5u)uM6%w`61pNv(JN9K ze_Y}e>6w0te%y?7lQB_pLvyYzMlk0PO8eF%I<5ClHS`3cd+n>Q*c9#TiJMW{u+7UwrO3#+Qgt%m)IY99=l359xGIcGaFsZz{JVAX z?`{SIe9L97T1@1@0yTm+qRdL;izoD8)GYr0vgrw98@U1|N3ujp3tVKg{^O7TGZ-v@ z{=fePXGfLs&shNf@1O1eo*||@LSq0Q^RVnc29=!*Ym9GEcN4pEt)0iz#AqZr6X@fN zU6FZ!&odXFk5MM*NgZPUV)w+|31@p{)b_3vKS1yw+= zFsYoxzs7*cgrGz3pq)q`^%jx?YsWUGf{L|bXre%b{!d*v;IE>tQa$EP*)|llkQX%N zo$iGgK0g;F85w@c$shi$bFxFwETp8cDAHTG4u>j+dB+Y1$dbrvto%q7Mm}0<SHZSnug$vg2VhP0b;8O8T;OqbC3H{B@2CN1is+9+dz0Izq ze$ayP5pPGNg!nv25Z7wc-WT>7 zj$uE{rx>zLPMEzi^qv6@=(gJY9G$VDV|+b1iV#iFWS9k(TpVIb2sWE(v>>pFylDvC9d4Tcb)hhYT#*)c~DggtFwm>n1va2kz(-)yrf(`VABBEuMjv%R#~ zy-&WE0k4UyiMor{qLlk@)5w@AtRG>AKJK!-pk+DTfPdC`FLmcRlxfMpv$Ie}e1`bC z6Bo%qt`_*ZL%J8`N;{%oUToNLXV^nzsA*f`28aSVbdh#t*4Cg@ zU2%O^98HoJ9=4jpMrJL;t$641zTShLPt4SntuQGyps@9S@6BGka5v?~M&T|u#H=6_ zntMg4$=}-0UmSSETV4@>oV4hE%|_BPQ#g9OtNLi}HyQ2UBcF_#p3O5M5~_>pB{EB9 z21JX9uV8o}Oe}Ysn|MDv)9M?OH{WYPgB|5GAm3R18_UL`?Hno~LUyoY8|jtUMX^m3 zI?Z}~TXh{+e7(U}H!W))e&H{q7u65kVi=O^IqZJVedpWy zVIA>#(%?{*G4xbZ^{e#=oouyJV5j(4B1ag&A?_6Jx<83wD)YBxQUt6K-Xx~3F_z`? z;#`74G0_gW4WV?K_I-qXskp+Rz>aVV@t=Z?YKGrfLhO zd-TzEQQCHMl+&fTjBf2~cfEVcpzXcP_tK(NHd>RD;dvFpk1Z`(z%unxumk+DQh^u- zZ$}Y^EjG8hsL4~;xfv*M$LeEGyEr>)?C!IgDVu>gR%8q%!d@<2%$G`FsM8djeoh_{o>) z-H30@m^vm}`2yVG)21cE`D|8`mW%?jHvJZ=zlr17|O#%W$L%%c2)7pq~@(v*w_ORQB!r5-Y0yWlu;` zB0Au>M);4c_ZF`WI*#DOcRp)t1Q16>xg8Mgp&F;$fAh-f60T4=Cyfyj_hB3C<&t9J zNwYwa{pLdmbavXu*KisM>yJ5(_%`fIBVsoqve2vc!vFJKXUYu8-o!nZe<#%sVgLyV zD5f9m8)E&USFB_%tBzeN&8LZ17(g_J;j$T*Ux>a~C{m{S_R-QkL*dk^+1!;fiIv(h zmgr-RsT6SYw_+oh)o)Hgb6dq>2%FA*fY=oGGm|kR>`nMSw!T@)t%lHSi-rsK1U6K0 zGP|ImP?JgBIm60zyuSNbMbT@koZn4buw5fT)-#=X<#qH(R zY23b$Z(QJIoI5)a~G5dK)6>w}?Jm&A^ zyToBmrcf3wG;w;DVir2>rTc4BT48r6i0zrMUAV+vMo`Rox3U%7Vvvy)y9yOGU%*+8tH9+>@8W@r4P8 z3Ey|E^`48ooU_@vk&1~E`UKTu5ixgdG#ktqhPUtk)JOi?HY+zd4LrMcIiskXP-_~j z4^X7lHBa;TG+BcAP`n>lrhOWGB;w7C^4kknb>OP1k8iOKju-J8s&a}rdd3}ay7JQI zCP-Zqv;>~NMOi-fBAIySzR_$Wrg{KmE?8Bq6|u4^jWBJkx|IH?vg(`eH%_h^UR?OK zWe}KSCgN+IxP0X~->XvfZtm&{DQNqaM%9Nd@{JgP`!Rftr6H+_L4=4jgKp1L;nk@h z`1>^af^qc@k_xCfpR^5aGMd(0N`LqiOTXc`wPyGbE$@-Xlq}4N6(Dc;_HO|gBq{*D z2Ir@^m*_}`mFyADt$wQCpwz*deHJECI~tgd!ACx!>X{>3lmxQ@WjX2GHk(tCwa`_7 zTc(`+H*?bT2aWeAjiq#@(V~7%zkKLA1g`Z0MpY$7P6HAwIizkySKCQ%<;i5rD6CC% zxkZ&pDfsqB+7W>`U-?*7dt&6RUYPgBG^hvZy>ely$**d%sR4(g+jhE7*|=6?LcYOH z#nFDE!XYfp{$%L-6tJ-FONR;^Sfn5^9DLZgcDT=1p!h8bMx=7U%rnl3z}tXWMe!7D ze{Y1&Mb_dx>ue*-=dF$?36G7i}DDQ`Zo+`cY5N>pA@onM5KNI2%wb z=_?6MzahqjQFIXCvX#uT+cilVW5M`xh*IiajpDhNlO4;W0x<0YoM}&OC`peMVsmg?TXsid z-{9`$>6UKW1jr%=`Rk;@J5qX?5o11~?C6iiuIjQqmPym*!qc zPR0hBBqWdx^O*0(RlkGX1x`Q-+rb;d9W(0(0+ro)4MuSi+v>I%)9^_qb*t`fjOpm7 ztOmyX6seZF3H!#hHh&OlWV^HX3KkK8yM3Z0m~_yn+~Nl@tN{hIazTuWIHlv#AhJ1) zn-mjxa;P9k>-H*2GWTFHZ6)&VG**>Tr|!xKwN1KLw3VQKj&52YK$ZaWQkZtWEfi_z zq_^Gw3F3}*8sicER}svZRGje0H(+oS+se_MkHF37N^riL0_}p^XY9s`^9E%T;MJkH zv2*`k0Wvtk)gCnK6v0oo?Ms>(wUNmhV>1^InFC$PL2F)qU%zHLG2v}h?D6e@6Vc<> z1f%+hrxsyzCq+{nu5oK+RIx_xZpVxRqNJd=4oq|^c+pXhvE@!?6+q~PZSrZ(mwXXN zta|r!Xyo~3V?u-0*YqNH{`iUnA0|~^cnB>Abl^`_;EtdZrDWMdqRs$=R5y&fJP2CK z<90h%_6F{)<6%7e-w-1+cI+mXS>6?9cag?l;@p*DCYG9eEO0IcM2UMXA^}@8hI-j| z+Epq>w@*?TF6ZUl?2?c5xGQQK)cv^+fTuO*4F(DpUzu5VZ8o}E4P?^&KB?uMa-tgv z*txl8qM}yWW$yzU0&97oNsB#q6Ka-DfBLdSP^~+%j|00Wm-G7CDu-gNMc1%QUACIM z>9pnDdgT+Dv&`{8E93Kyt5>;)6jY#K`G7y5$pFSf@SH|TZ#hy8xR>;x#lA)zEzm(@ zCsAqr`mM>%E-?J!P+>@24m0V*3lP?TJJPUpM4_1Txr9E27w3-9j&2Vs5fFYO>?>sE zbF=St;<$T~|Gv^=eC5k_UOsEg2w#R(QsUP*aE!}X+R7O&vGkp#_Rl#gi!VtzHrCtf zd_OMW(;fQexKnIHt~u;VE<;ypg`F=vm1Rk~-Z&Gk^wxUTfZRTpFd2z}*)xzSM_BDE zITpz0vZas8Aoy#v}Q zkjjbDiHh;hvvX`3sHL5f9`B>7Q6bx$@%mA>`;R+ZOESJ@4v4vrql=TrsL$v32~bs;I6qY_sZQGo2q z{Km%TuSrKDDCS1tiMt5RLZXiM6&mn4?AM@LsfX9?K_9gZcW-@^>$!6|>DC09cJHJyQtod3)oUx#tD3L|fOYT*k{cuJ={>;+2dF2ix?rX6t4#_j-FFWGZ%;K} zfF$l!WH93}CGM^J6k^eE^GC(j888{gem^viX^${#=<6LVnebv0C&>G0=l2F45A2%* zV}#R}*)gHh_H{Dt0zd8vJZo^USu<>%ngLh)8TR@k`%)N~Nak4zJ89ag{DTLB57rTP zNgyYCbFJcr@eEGSoauqCd)IQ)w zz@nq54en|@2+|y$LMX~Bcw{hW+O#c^8ElVssz`M^edLQec#l&}5!7}t9KaOd{Oa`G z`2(CL1`#?0eUq;bfQ!uL`(&-!JfD&B!IaPEkyvP7ecgV(38YF!!o*}dpTQ_EE=>4w zM)f+=PC#~_Bk>t0M0UPERlGc5buau==S@oWol$DN%VT=w7cgisx4l)(uEC7qx8~LR zm5)ZUQanfFKB%ThOWHqBDxh5jch@x#NGM{!o<V<&Ze;?la3oT~7`WL3BnP2u)hPjP^`jEhuEw>FDx zGN~wR9sI(36NLlUueM?lFZysObw-B#pRoQc%CdwH8g0EMIQku6{87Inm5U#J zt(=WKd=O_4dZ6+eT>3uNx)4(G#>lAsNA7d=_5X{kuZ)VaYui@3K|;Dqy1PL{Bo(AP zMY@}zyHPr&r9&9HySsCMp}U9pM!oOneZKcyi*@nmnl-ccKI1r#Gj`R;UF>fUG@)2L zimZAsRdT^F@E}5^{HFb^2alSSaHQhh>U9ot6`xYJFs}jO`Ho&qMng~YEShQmorp>D zd<^lvS=1FWD3J8p&XeFA{=A$vC1dMWpT0o6h36b_+kFA`s;eMSId(W7G9j=sy?u2YbJsP_~}|&7~;oW z_+ZY=FIN~<*>91OF{9;p1Un}NMn6@S@SjxSv+dap;14}EzPL02AXOlEOR@^yR`u7l z2B}+Ujbqja&3`~`)3+L}dsEZfAM!QYV%CFJZMf=aHh!4E46R8URjm9Ojj>DIOsMzf zh%+~&7izUj-10|#?8ltoJU^kQ15(t+nBYu@SuHp3dLFZR5NL~*{Ar7g%nYo}n!`a! zwR6x6`tPKYpNqyVQeX)a0m0sz_kR1V^qHke5;+c-(<;oPv&QhH!&9;s=Hxtgn(#dAeM={Wn+M?_#P_pvh!ldUZI}hwN?|X3dV3}w zalbff^$NRe3P0Rd@nTQOiG|aA!}(JJ^0yFV9u?+d;zlS(>`|r#iiizA6 z6XBLMgAR0^93wX%lgQDE_crB~H;IId?DUKE7k;p)6qV^1&Uxk-yq7)RHdV&QqTg#) z_r0(Jq11?+hX$$-H!pVs2X$Xl6?GrmiiQm_*tQ0gXPr2|`TV8D ze6Z0<^RR2r(?!j8u%BnY+GbBit&r_55 zuEHU~GC--O*ytU-;UIG?!BwXmmQ!D5Z__M)*oda=6nOV?+57MOp_hLhp2R zDI>mGOr9_-Oc<-~=7|xSBo(_-bzR<4S>8x6Ycx#x{%STKUu^dwhE$+q>y4|`z>9aWn)Wn2h57rHIe2WiTWCAZ6ccW2Vo&G`q8l+-1+W1_k8=O%*G&^NaTADap?jB zqeEr#?38e;^{yDXSqaa;BiAd2f9ivNhno~Io>IOYh@wKfBuG#vaHLQu)j3lLPygiq zXe&7y$R65W&&Od84+rtVJ?GC8Vt|H6ADlJ^(GWy{KdB4g`=LlcU6TnN3(7j7#lkk! zPZ`AeE7~?PzQCxu>vpQ_YUmT%s6rpqGzgkqkViUp>UZqIuIRXt`Pi@F`o=J#*DF`Z zev!o0=z_#``I`zB4S}toAmj^MZIgMX{sFJGcdtkUMQ4rnT$92QFR)A~)73gi*9Jci zz1|+G3Z^A&K$rky9&YZ&4F&3zQ_9$aS3pw}{U-0g`q)UqsqJ-NIiW@I36f@H#7_`aIM0y=0g04id6+|iw=~-Y6aN0wziC>xh*!o8>@&b5#&@* z)$FQw&>)Vp|nYfv!U;u52$2>$&<8pSqujKt4LzL3@^ms3vkMp@k9Uy3H z6?v>pQ+i*zj__le`sV5y>G_!LOL$panJ7#6M1uIdYPD6wyaYYowIn<)WLM)EeV`X++N(e;+Z$(N)DwYZSb?4n!jD&;S&TUhknr3>0=)6A|M?dz)(u zcSvP`+Vbar`=5S82qh^nqnL)jwU+Wg z*!*$jc1VO+hCT)3*A+7Q$X>{tJn3xa4TnaKYiYZ}YSok*qgaq0(-_n$8<($ynYdxSVd`VJ-w6_nG$!ofkh^g-aqZ7bTF( zRwh6z2}EMFg^m7+p?*9u|4kv;L)5mwY1ps+zv3lxC7qJJo2Av;0ms7tWESZxHA< zJIL1by6ZN6V#1~?v2Z0L;@LnFUZrouu8{1S@n*7XtlC^lnPuuJm#z_2>8{B0>af1) zx%J}W*st*Tk^?Ck`v_W57pZmVhEVQ#-?ETA?|#|D_+Y+u-+#}KXfLQrQCbxX)1dA3gQWAX{nX? z%N72S+kci8_PZ+EsrJ5I?ZR*G#S~YevDGU3qhS_iQ^$f?({3w-b*Eb!a10sVvJYus z%XCFoBkZ0d7OFR%bGZTB=pOj^tT|s}$$vFEf6Rr!r5I`{n)_PnTPB49ny2&FD?0C) z?YfB>smh;8&K3VoDiaZ*_NU+x_$ zmsjGxw)v{qFTvz8dlJdy7-LDJY@};HE{43>tOgteyQ`wEM)_x%$+;YsZ6w0)FrRyM zPczgTEsudPwg>YXKc<}ERtm(T5nJ}62zYHs)@*ZGCHKOFJHO+5FcSKIB`g1Gn6m5dfVxR)wh6%#aiw`@_rgg0 zWZkoGIYudDx$u)K|5EVPiO5mc-iL4&H`86f!=d*vS6kz-$m8<iSqYfcjlmVn2WQmIy zwN8eadSQPkiT{$ax7pXfEuv>%-WoF}BK{}`sZGH&N4>wDoMh2fuutu;fm(BucA>>l zbW4&AMWM~_dW52Fr%M25R}r6*BN_8$7}$=5v3>ar8eBWdAgN{Kdk5pGD-qYcs~G_HpF)UO=>I z{4XLuc40!K!TcJZ-rVxGX6kX{48`^pxNp8HFE$MQUhZiV1-Ss#YpiNj%Gy<0_ek#A zn9i;o6l4Oas18DE+>Jog_~_h6Pt>CUp0m8u?qxcfAJK{`MWZ*Yjb7v=8|l`K$7Dsp zRR&IBP!seOQG=f%Lo9l^-46{{=|3Du@bZAkaU?EeNiJmWVQ*Ij5mA5`>GZ0VL>+_11}45rVpNicYk;VL&v|MOgE;@vEy-QRUf!F9Krmt$94mqTL%;&DW& zXAmtWW6a=ew_6K;U518ZN=zpff~AA>p;@oQxdu~oVDXGPX-K=xDY}3ySFEam@~ z_Zi{~hlnn;N~hmPl~qI-AC=q9_zyMvKc+Xk_jfuIgjjSk%;fQlR?(Hc-5+xY>4MsA zv)F!B%8XcWXqFtq6!HmgAG?>70yCSE!!J4e26#uC7o;+_1ds~kUPtq*e zI0eg0Z$|DKSxWMqP4joOv%<+CD}xs|eSssnGd&-hgEw0p{$k|+c`CF0Hb+U+GY2l4 zE`fpdU)}oBY{W8j$$60^UYw{WX~YZO7T1t^1RAubnyWWT-DD+RTlDh*YxGa)TxTb2 zom|;6Pi$@vcx*ZbJ*}AqchF#OeI=2!+C$kJ;V~u+q^3hZAYlA}ImyO{MeU&wKa5@Y zzaGd3VsWB&d`3%rCp=dD6)U=+#3nU4&TI0=d6!P^`7cELrX!bde{RIN& z4>S4@KBm&L1?)i!oy9EEZ<{N^_DKI%fp&f?&_zfQCVC!@A|f9@SoEZi4H@Na!miwu z6oz6oTb<<3%38B2714k(^0f;wo1S3$P4OiNk@0zeoe697G&@D!RAk*6A zlu>r4nriZFdoh1Gc?6>*p#yIrdyJD0Z#bp}J8>-~4h(S$gO>u``D$j2_zi)3-8p3WN&?NU~0J}#y#Q+WT~LOuK60xpmZ6CcAlr*a>r2m zL1V*Z;|`yf?C_Kc{R<{@^t&_*!=Mlgrp28AY-SVc(IN1|Ju;I+Zpaa88m0^4v1h1| z$@8Ag;8RG7e5Yt%St75Ed+!L~^4$$&mUww8P5lowlB3WywUf`ps6ldMUZ+!0FQzz% zC|?MTN?#+TCA-0mnStbIzra@(;&p^ zB-x`vJslEe7xL#Fu(;VGuqYg){5#5yr%R~tu3%%n1|jk`WIx}4W#~{p$@NVq*J+8q zZ*VO0whz0qbba!o96x3253&G6e!ep{Q?ple#8oTJ-nmP4j;JPSORbhAOA~dZX0?S? zsEyZA=utKrQ=t@H%l$dID?ZfMn4Ggwq#Ubx}4f@e%Pm7E z?P)R(C7NNjR4~K~wJsMQmP|7+lihr`J7S=>Q{L1Wd@!+I!eXyB#$}WgSipB^yrCY_ z{dc<9KYrTp9@-Ww?M+gsS3jwv^_IHxoz`R>Iqt*6UFy z1XhDieuI$4&&udJEdsp80BMI2=0d}ww{EW8elnoC@Z6QlFqwvrNK~qFUJ*$7V%X}^ z%(^$B+SSK~H|ya|D$-XRY+1WxQSB} zM)o&RBIf&r<^}5M91jo!P^5^08#0cBtdi$vU9uxeu#deS%t}81X-OR?TM-*}&SZcC zpn|4=Go1*jUU_@O&6L-WD1tPXld|FR>x^hW+;SAn28$F=hexA&uP*H6;OcJcmAmEz z0$2nc!_MbORJ3Yem;m(ti|e@L2VyZ8Ap04XCz3=2R^Yn5xQMpL!}JJTM#^!|Sy}b1 zpQ6^skKH-x|I6_`{#HVt=+70}`&ph?fSwROv@!k%MO)*(byyK|!4aoq@0OeN7AaCv zOB*XV zTG;m;E+fw$D>NXk7``!n-sC4?3+k$>Bu}y5vN8w+b>AC^SAHV?r=RCPh_Q(6H(Z=M z+t;<+Gl5_Qk|RQ#dQ*mxcV2gLGGQvZEcP$URa7NjZqI&YurF=uHy z(20dshVtx-9*% z9fKDlqaELX%LR7Sww9_R!-%{9ZMwgzZo?;GuZ5;Bt4NnBok=204R0qoEx{q+Y>T$_ z=o&$84!no=&b@*J+gh~r82ql^xjK|{Yy2v%28Ciyd;HHybz(Ewc+^(>UjOfvDxcmoFtIIG&K6TM< zCkhxm`KnRN=wH%>%^IJqNH$?HVrADB^!%`xNSYIivFU=JN63=gYPJgW7co<*)9R zLlO@^VGt0L=V|sYGn<0YmC3Za&&Frfq~}G!1ZbnXXZIRl>o*yIiFeoXJ(mO$F7@Gu z#9|t?MD;(}7n04a`n4^tyBxWfEC7{NP6yxKy!$ zz}ByFe>^nWsad+i)$W=(EE&wjXvB!yY<|?#E8!16fyF&4Y~R_ag_kN*xUw(h%4Dp| zjT;rhjbX3SOS;n=O+X%U?O1y@0W|S%l}$&yDM~fwykiOA?lFKrs3~Ecr>@KT<{rC~ zNIJG4B~;w!ln95d@d+CE{hK+|1dve^Bh3cV#UX<$<`Uld{22u#e)8%T#J9t`1~dgl zyecK&ZLq&QVtWFoVH~zhn~i`7?dn75vp@Dqd^i(%_-dE0Hy<;qzltv&j3$-!lF~68@!R( zLrxk@Csk>8yPP*gb3pRPDbnMm6X2_*r4@QThyvda3)Oeu87jT^4bCMH$wRXCUL#6sdAua+;KFYn`m zxuLm@cS_Y>XI(eHHueIiEcn!$=lumN@%qBL+!Gd%qnlh;K({jF=TtWv>OQMcWOcSb zce{54j(>28l3hOzH-D&zb#I%b#=vUwSRv{2f0-jl9BU}|+VKInXfoN_gGWE)-hl*#t0$znDdA$bjxq{E5+LAr@ zGEVku{CHxaN$zaA_7^>bNvmfKy&O>&h&)INKH|_edLwbt(Ux@ii8&tWEf zXhnWfqr}2`k#Fd>S8ptp7FQNWN+uU?#YZ&A)n6)S2yBsXBajZm9BOG+(sN&8xbGS( z%!lT{dRz&(UQay7E~>o`FG1B41bw%8{)oezZ6IQV`=X_ksor#8XB!@ z93Sq-3n#Z7)X}5d8S6h1NJw|r;nHeINNwr@g4dbwb#zLXI3ruoqJ zc3Qhd2pEbbxd)oaoAOTMv@7_pqFi+!&kGTcBYA2%e%B{-xO5Ap+uHeWfD(l|cF4Ps zuc7JKQ-CEHkCWZNyHeVIU(qqs&kbUK)9ho3eC| zxGEHVQ+w2sYrg;)HL*aEEHg5%^d<9KANx6Iehc^J=mK!`v{ur#Zzi8=jne$sI$(1V zl0h=oTg%cof0{{H7JX5Opr7i9^A10&xNd)eUJ77aaTSwS3FagIf}ATav5p98ccdHq8L+mBu1cM@H2r~F z6Fhl|UVr%c>d3LpFIHbZAZ~SWKXS21zPg;(m>y||3m@@OL(Jc^f8S?L&3pQ;l5mOr zuCCRRxd!E*DDV>BZ)=uG>5jnXsJWSA&TVQr!q!s-K={KRYPb? zj@uk$9Vp2*Jv*|>(?LmSbI+IDu&>@wLPH4T=#CaC*Eyk9KPkIB6~^T;{+{`Wr)tyO zslyesHKv^!CPoY*t_3l0Cu2+32w3mi{wV*^ge)JVx!2&Gnb)_p=xMEbi=}rrPbXOi zJ}zs{#~Yt98==9~g7*d=xl0ygWc&E+S6NSR|0CeK6;{0Br#!?<<}-l` zUv5gu*zBg+U%rjI;kz&>D26l6EKKE=5NBj*<&WxB5%aI4aj>z&+x^3hUVe+mf}zQa zm5gV2zG&WFzVJ?wHH(+$U|T6P>=8gn#vIQDGyJaOIClgW>1xsTYpuzj*m^%yVt z#KcEH!)-(BHi3@A$vqoRefAy+&syhfCeLy)Q&y?W>(k{IwM)vMCsU_YHgn15McboDX@?(iQ!Hyz`rs`GgK@`6tdDCI!3bI@BIKi?em$_!yv;U zAi&Tor9t)$yDR~RMKfx`XInp|%;mHAjSb$CeMEps3l?u)x0tdcuGQK`$C39sz_y5v z73Pvq6QDV~4Mu4g@(uMZY~YExpHu*gG;VD~F5G5Z7uRUt<8o2FH)2fzYE74~RjvE1 z(?Z51G|^HY*mQsKdq4Fmb!Lu8kK_|SKh!vF1>)z=k$bBNdUaL_)IGY0u(#l^;htB} zx3cL1#(0X>%f-VC}32?|c?2^}Tvu(?-;!|QYq zFgCvOi!Oo=p_WGz*zpS_3ZwF!9l(1Mz}Wa1?wpGZ_h(z?dy$5lohC(8MlX1u$hk4a2Qfe@QXfiDMq9sn(6A$&pgWHpfcVOCrY04US5z zK>IAfr9_E4slh`!M{Vu#k(Bm*mO%4EC?|R{fbKl?R1Pl4dvN?*7jq@`=@DX{chTzp zHPv$secq|4mC$4uL#PlSdY$fcwFJlzJ+0nz=z}-L(mu?7pWf)%5%*B?Fc;qz5m4O~ zav00%3BsHdp!cCT;ksrMHA_Us1svu{6UNf6mY=ZHyRneqU>4ENx$@S=M-za7ykqIZZSFrFFwJ8!l+*rwW@1AM*5xvuVL;7;<*u441L=5F5*przq(%Z%3>_RVtj_fv`JJ0=#ht(dn zg~HO;4JOvQsuz}<;qNBg2Pg}}%aJzQZHWc!ZltCd#yruFC*7z-YIj9bQ2E51o`~|u za(wwmzSY_kriwl8o1wWYx)Lo4J$tQm9_X?y{?;g{Tv*4nOJVA+oUL0wLn0nyyqBg% zSVq=XZ|3Wtt~%Ddu3D|hHQ)Z(E%=vS+qZw^jea2axHEOT+o4%8z$#8{b%pf~gS8oSRb|$` zQmy0pIv>5aruQ&#d0b4MkYs(2fO^G`7o`cN^hTXD&e!2l=P8-W58Suc5fl0zVf=S* z#Gl0tb}n(sdo#Q$+5vOwiYdHc*hlK0p;XWfspEk8Rg6FEE@SQp)&=BsT)69A0 z?BsZo%SQWLge=)nj?;1^abSA%z1Zeq&AX&yaxme(O@LcHN~r|e>*qHYsf#+zr>K=_ zfA=|Gvi%_P^;vfBGaJ7MX~PL}n5%l_X_|1fn8DBK9w4#twLT!z3}h48CUQrg;d@8Q zXJ#7*-iRk8S7cFO*(=3a%W@&*QQR#gdH5Y z6z(q<0~9c?ZvGJ$afEd!%mQL+{q-X0{{P#ze(NKEm zyZ2;%4B%0bN?va7piIHVxeWG$$%N;{8@(?@772#&flvGqM2Bq23+)srdnz{=-w%#+DskY8l%_dw@z2|#a*%R zxr+3HQR2Z7qBUN_J-jR*(x})N(^bqHFHRgMLwwv^?vvOQWY>UKjtAZ=d-HKk`Ez7< zvrcz=a|I=<4&H)~K*uWN8e1X`6ICJsr`j~R7?!jo8wh0BX_QYd#mD&slk3qP6!ZQh z`GNAxGzV1)}6g=J=Ccpah+lk@96zWY0Z~F_J$VpAH039GtkrDKY;R@lcFhYkIG;l__ zMid!kWmqM2N6kYoikr<_-tVW-Se{!-#8&D}Z0T{DPq0&R%cl&)U2!(rfW+Bp?p7FgBJpaZ`R#eVd@fCcoBv6n>e=rldgru+g?zq6qlDxHxAj zD&%Pxuz6`brl9XJG&V(RyJZ}tMKA>#+ClZPoGnvK+B^1`ETouXe{qfgzqh45(wmF(+mb*<h5aWSNnnZ$moibTELw z?BvjfDG2j>i$>n9=b?f{0fTy=@UP zS-|b;k^aClj<%wW=x}EDOT$`j@KZHg3?9`M=*tWlIJ*_kw8Ve@rS4HtY#V)NS6vw) z4}<-#F@3jLN43mkI>aZXya_Bc@9U;|oIdY7O=hM<8coF8t1H*^Tt22C1UinGF6LN$ z?k1n=hSiIAArSm@`>;3(dA(|kBk{nQ)Co6Dqlb{4@T+Q2_2Q<=>UjAsDNBruZHn0- z(&mplykn6-=w#{8E;&R9GiP1bOr>Duq2t&yJF4)Ty~#4cAKdDTl`W z!E9r1gRR_afZVTng}%xBIORx>aaDEOpvGajHH&gSvgDyH#By40t4%Cz@~+xL7i1covs7`z0*k80 z1nNJEvW7IAD!NyYFqYwNH5Hp9)+@&w;|8?uS-{kJn&f#D+7D%gS`m-%_HU?WtlwZN!$rS0(%2Sl z!`RzN!vM24CXMphN##1}YGs-)5v0GmsT%p8eO4rJS>J(iUan8Z&ZOJO98buzQB5>% zG^fmU!r16xYot)vD^k)@8H?IZ%%ax^y4`8py?|W9$Vxk^N zgV*aoud7M>7nLvnodw`D1lxvv5Z&62sC)LaBL!eb5CL!SYCr=F*GkwS`tI95-uGo_ zdz8AoyW7AZZC|0*R3$P??hXlHwW1fZ-QsOORHc+D?B>&K3GB~n)7Z*+e(9ur%^>g9 z#jOZa2#3kuMSwv^>L$fQ_qAG+Qiw@`t+C_k5Ue}NcBObz_0%7zwUts23hAl+j4v}u zuFJ_rTG=PjN!~Uky4N9nV>yXlc&rp}x8DV*gEz)jkE-KQvH zbGSH>T+IU7Id#Tq*V%vA!$^buN=)MP7WJi@m-`b{1S}koa$|e8ZA8B9ZYe4_;S(w6 zsM1wbbgv|yz9f%66(Sw=>0J$VooYt!TQ*(>lGrb>zr6*)JQTa%1}bHGb&RxGe*M~! zOTh>D4LPWW=9@!z-j!2JwoP{^@+Be=0Ujpiu$+X$ci_nn=CM?w@sy5 zd4P8Y)Z9+GjY-K0vXD)>zOAQ~OoyZQQsX0{l&YgapQtuJV0~+tgU=SMWotAmT`o$l zzqPf=Y5jcjqtAV|=Oq3#ze3*B)-%{m@ zidbffj}a+EmU3~eFJ!ttWli1?^nBVW;Q~p_Ma=hz8^6X6!q_^?ZKbt~%M}Ds2X#)G zQ!qzHa6dq+!WpB8>T+Y3gchedw_|=(oYSOAZwQ%T|5^$R929^3w41c{gK8rJaH-G^ zqG#XK-^@-R(&J@h{TCLoBVi@G_jE&|-RG|QBCQCoMiRTUp-E?_(PBrotjjHmWG#70 zECN*|d9S&2-H_U8)~;WRuZnAqqPSQygG?WXa%qp%H?xC-+Vc$z;!eb`?|%DI?C8ZYCljFg&lB~@u))xBk#B~+jDih)a0 zxU2G^0A#gzr~I$Vufqjq6<|1|>v~wAYhouJ4J(5T+(qaElHREC zkYssz7=AtZl8ciw@UV=;U}ttbL(fO^f#~fUy*u?RRoZ~onYKw|_mjDl7sTB>4=N3o(%G1t##FXWnCR9e6CAEDbkc zYr2=a?jmhd7}h~;qYA4eX&d!3s~2bwEc8&S13Y|(<-) z;1l3az?Tl+KvS6%FDofQufO~QdotE;w zj8#g@cw$R;J41$J;z9w%*vUnC4W_Q$x^{Kh1hXTh9J32OkK0DxS*E8m|01Zu{$~ZF z*^1p6!3W^I7rK zjt^&k#Ad2ZWyV(Wn7DX$a9dlAmX_9W@2|NdIrXQ#qWL@mq(T-W5~SZw2zG)`W+4^|=<*Zo^H;g6&%P%C1{)tA z)5J#gLwBZbam~M&oUNZky&-?pXcuC9>nn*{!pln#(OXQyCvJa*dDj_s>tM{uvq;rz zq%nqrVDi~zAQJm8US%hEmF&Kk=UI?01lk>?>?%*;v>tT+^}VmnzV~w>GjXJ2lmI-; zBf8(fl$l7Az%MUkd-MaVgLL7G^Dw}^Eyp`yzjpC=6fSFs=L<`dRYSoqB7b~mK^;iu zsM}H(yiD2VuQbtWy6g8^$HJRJwiiZr0 z?)Bn$P=8W2rs|0I1aZ)@m?;~5e&+n_$aayPeQRE#bvzVYyjt~rc zdTAOKjmd@$3a@eD;bvq*xH)h^5Psnu|G%cVBdmRxsem<80MHS^(F75a!Duo*cdyA5 z;x{L%L4mtR{wEk|z~`OQvg!J~s08K9+#iOA=zHj-#RNoN^{z`m-6_IY!}4Lut}zyS z7hKAKu$FAO?5FB`tj5BT51StojO9wB(yyd-oXNcR<4US+KSbNih)~ zO_<}AXauTV5rlS^9<$In$AhWtQ&_@6^(<7Zi>-%QG9Q)KHwk_pW4nROI;8Lyh&Bxj znr&37MYCwx+p=)f=a?@fxj==hgNI326FG=w@Qi6IoBgKOXk*u-Bf)I${D?ec zYY#a>c$KhZCtBlGyaky!E;n>e80o&O5x`dkU+-5CGqt@?bhXaNv#6(xhPZ=A^!XZ#raHIAWZKs z3jtl=3$}&k#@reZo(6rn@ST4HdAHX=Vq}NtbFx0gxH^*X$+Ttgy6rwS%Logfi;I&E z4`?$?1?+X)jR+akpGSv-dAarZr6h$o+#<0-Y`0dt7IIgs3>N9-d8Ca-lE)nqGhD~%zafZD#ALbPW<|7jKkN$@bBDh zd5+JLQ|nG3iZODJ>MQZb!JXsWzQUaXvya+!UuCs^ndP5asMmWlZU|!%oN8FPO7=<) z3GWimKE;1b5fgSV^^bGo)jB*OCYtyY6I4D@*nuu(U27{WgitJdhYIA=JVy*|TMfIf zxgg0F6qVbr_}v%NbNh}&p2`af7GIUjmHBi;CCT5PC*EMch9au$SAO$FU*4K{l5=tJ zqH&)|dyK#la?@>P_JgBQ(=bev`5l!y_z>U7X{SblNb!_JtR@-uNby8316N2b;8f|P zFD-z~2X}nLSS4@0xpJ_}-SDE!M0zKKh~w|(IA+Nsm*QMQzidQsJNl(f*4 z(8eUJ3#&%oFAk~A9h@F+^MfipMZEm~&?W8HzU_xhN?Jg>C;x%ugX$F*Li?q>3!6n- z&t%tO4y^fjBoe-()D0E3bm+9vkjK258W4z!RdnNXz1MLMYF%DTB_34BSE#Xy|D^VJAv3e(R8r(oPW@;aNQLaQt^4(td+ zTW`NT=%9tMxKavZ^iSrs7%-qK7UgnAN|`PspyL}?4^+KAzbv&^`RHt#ex<5mN?|E> zY`dJ?GXK3A>jGEQNt($xz!u~C3lzwYjk?cSqHhYGn6wC~c^vX9txPMW!-N=dL;eEz zN>VDjlN%-Nh#R$LQn`-LWm2g&B6};21y&*K-QQsS6eJDy;T`E?-h)m$JoS6)E4~Oh z?Oe*Ptfav*Gmf_tN4UAzgf~KK&J?(FkRU?2j zBdKrIUE!3Q6V)m-zh`Py^|KU++>MZ{Is8=czR+%*izM%)xTnV2TnwBBR4ruxFw({L zx#%kB(pxS5$@|jYw-pgapoKi|6YgtitLRR?D|_RgLX2eE!L|>v76b|5YVRkKLVl?8 zN#LEj+tE~isM~W)C`hHg!|Nq-+Zg2j5ARO)3d(~aXLWhp1@G)E@}0ivzgwb4-~fwf z7b1~dlpn|_L9q|`%z4M%q#dJxB+t+E%V~b2-)`Jy+qu1-kYQt5R0KkR2xwZR3AWmU6jeoJagz;?*iV+JjgYOv>(mWb4VhZ5eL%9%N_s}B0P35P1}y_L*yJ%Vx;1-s3Bxg7qLhb<`o+h`Kp1HhiEW}K)AIQsc zrF@+S4Rhu{Aa%d%QG@Xi2gZ%Oq??cr=MgbmrXiNwt(I8FsUFto`8;?P)ibOef6_^_ zs^Zs#UV0d2`xMKameNFXczL3u)EWo8K)kd{q8iWnLTJ0)7XMA*Wz~R`bRn{RcrFl8 z`?BXJ`-Q~J_AaB*;9j?MtIZ7e>+@(7hEuR3*N1}KT*uE%hX)yCJM=?bX5()Oyp5*{ zH-Es95w@*a$ItvrUsdW-+kx;@$n>fm2F^5MgB8CJ@_@H2xNerqUxp1J!UVT}b2N|4 zaM&1GJGA=Rh#NEVgi4ko2)Q28Zxnuk0<~e*5x*r^`vH>VYSti)!G>I!*O5xD{gXM2 z9e#TvNHD|Ug=lbIW!IMG3BtG!G$f1eAjP8!k;(wfn3EU{$2%x>S|nWVv%>O#WHy$> zos!og!r#(zFlcAQt6P8Bwd&yuQk5pELY1h~lzA&SMJBYU!|r1hCVDSnI+x?$M|&T8 zaXV3#))sk`EqEGMwK$o0_Wd3stM2KGq6NeK?}!tsW=l*kuDVy@pWk<`hAJGB&rFUl z73#1WO&rJY{v_QMRK0J=2EfSTWFFB*AT zFy?|p;Owso1trlI_CBh&Rhr#OR7UacQ~Y;SUmSzIuWZjyEBM7Vj7ze_qMUL+zLDYk zV|&F7Jf#hKFL6Yj&*Ax=TZ@ag`&0^69{f_Klx_#F!=;BkA#c}jFMN1>G!8J-H$NR{ zl4g)rY|h==WWDb$h4p)i5MTZ{0T>%mi!LR7qS2mZo@=}ctEKCWhvSm!6@yaN3DzUA z`L=a$4$@wxV?j!{)xQnqk{f+fWY{8xV`SnE>+8c>7mLGT4PMq*r+WeTsQ^!?f+2Qz zF^aNZ^#!$Sx=S2vbf1p0r5Y^Es5%4AK{NfX|Lgwt93JRdDRpJBq`Ezd!NVvjG22N% z>qGj1+~Lv~cW+2zmnf}Q{(0tsK?z_l7DR>L_xD`=u3iDp;&FT?8VVe#5;Je)8|;q3 zbV`|c% z*ducESqjH|+;nnIL4~6@L2fI!4HC1@zQ9+RekH;s`J+FNyWHy#R$oEu?&qoF=U>vL zIitBfo5YOKSt+j>JihU-AR%Mf5vEY{384^4|M<%iLd8{=lKmn)>_();=8i)n_}W6C z*|fLYb!6celfWch%SAy;+`4zIkk2@*pLLM9l0JseUUS-Yn#aa6A#g{qsSJ9z6d{ye z4%G)Wm2G0QL3N&Xa&TKxFglmI!n`tC6 zcR%lP>%4D@>mc*heQaWmMN(YT$InKm-P#YxGtj;`Ve960 z;4kPZczOGVl=iROLJcMXjF$z)s`eQpGY220g+-(Bev^%0^G7hyVh)X*vvf};yUkT9 zjWEiC0|V}!$PXH;DQ1id?hm3v8j8a7N0tQh_M)Ss7un25we6M~6#Lz+Y4Hr8DTH8C0<0lPy=f0U=D)_N@m)4f z7ApV=b<9&mZVLStWs2{ck&LN|;`7a->qgkq2&v#HX4FJ$KL_yIx)XfK`ldoxc%Y#y<9VE|6@JpWlGy zODLbyF@mjt>~z1`!~>-{2>e}Pa* zGrz0lph3qSZ9|*>v-8l9xpcc8{E<#UQ*d8`g`&_Q)V>rx%f3~)_|n~$ zgd#Alg6*ZhggO~MApk0@u+GXPDh3{qk>E6In!q7{n*3mikQB;A;AY`@kykxtO1W>B z(ntX)5mRf}&5sI3!my0%Bc8kqK9e6gB!pu;^XUsA!~@JNq*CXmrdoaKnQT!NOucDr z6J7ZEHTlclI?Xei9ae%k>2rTn>SsfVprgpEWD*`ByUW~~(ujOQy&SQx^+ww<5$}r* z;QkuMHH<&*tm31XPTQUpJs;B!)wDvxM;k3Q*y9;Sa^73+f_!l%)Fm){`#~nYtJr&| zuU`g=<~!R?YkR4<1@8lRq@r*g&a;|bc^fM$f5ErYUm=pXIl%6&{DF3SeBd>zK=J!OpXc%Q zkBTG0mPuEg$(}yf_OGQrs@h49#yRhFGW8VDs?LT_dLV2OZR^bZxc@nR7`T|7bxvSL zxDLk(w}iEZ=hZB)AN3xQi~c*PnZ5o~3_C5XD4FH{H;uL}@YR{XWa{6}^0#9Ni2l4| z^JV?Cl$zxmI4^EjekNPmLEhoI@PX>_8=b+_YyVDhqsw}2CYz6yZq5!egNVf(){D*r z%=;tg`yg{9V?M}V0TV$zEw!FK1%*Dx_<4a(5vt$N<&5dBjcn?_myOd~j(1C;L-#4s zW@MBRd?=eN%yH$N^^Sev$pR&*&W64mz7*~M0=>RUo?$F-m9rPzv?+~c`a=J-2WcmX z_cpSgxzA3or}E*9B5Dry{2z{1gtA7SgNSkqTKKsTGYbsvbaao*UI8~8lCReXu z56=+>i^XnuS>IXfzf9{5oNu>vr3w0Om#F9tw(_IY49hriVD=(O4R&gaa7LQsKk-vd zsHXnhmZCHbV%%3mlkK(|e@90X^RG1Y7gj_C8YgTWv3Rpp6CRJF-Sh9HT zLc9)RQ)E8q*TN?;-B8XP=mEGz%QwGs)|>aQlNXQ*c{kql`#TF=a=!ANxQ>U z9in#eDn(2Yh^Xe(PlbqS$QUHO9q-W(OOq4}ldLt0A^hcWyzk02&T=C=iVS&ZjJ&DT zX+$eNr<7~Tvt7Ypk$_uz+|7!|M*U#SmpvXgO!SM^-DliGYc9j==jR>6%f0Z?BYtnS z@dbY+=R`#t4AUrBpy&^I9Bq@4+U53ua@P7NKHB1bLQLH3gMXRH2E)G;d!hrrd{T;& z7T!Om@n+k;{iIL#3mHDE|Mz6!S6w+T0UML$TxN)x6}GJ%%AInF2p&%V`_xvZo;aaN$sla&dkv^PLK?MeB|Eu;26gEFLh2k&gJ~>)=h5xupA~I%|6@^ffcRYs4i9`o;I_W*7mIZx7Ze-uMEvi*Ac$!H;ts-M;PYf;p>d8FujryG84tmsT*u?(1z0#~xll#>kT znePST8-iYKbFnXlJRL?o(}xF7v5J#9pVd4kn#Ink2c+R|DwsWpS0bVj<`R!K{HNIv zX&an(x15EDl=UMA|d(so0V>_`q^sO{0OJp#6dfE)>N_fBGicm0nt zbAzIDdJTu$SV)ZR{A!%kz*VI%Q&ol^P7AN(kp7T%@GzWyeqP&BvV4jji8Ls$ilO)PT(_R^nei)xjUq{yy~aMigwy2;c$y%kNQQC| zPlJE3^Lf`&U6THmPduUgPRYLeW~<^s0?@-X{cV<=^V>wVH2yF;?@cMX1uXukfzS>ZAOiOmd~}Ts^n|bm^nSwb9(*wlNKC z&r}t-rMN-_cjXFiZ}aOLs^vyCm=xqR7-g?=Jh{gWFHu)QXUHRo<*%|mnHh9Ji8_HV zjdciZvFz8X*3uzzX{_3J`S0x4Yj!IHh9SD7{-~QB=u7l%SiganhD5b}o(*G%?9yv|h>0iAZGt#tLCblypNerI!oU zjH`{;x7{oP2&DOORFgqQPLrwofj%4Sq4VOWBu`^nI8(SBC*eYlP(X#5b1~AIg3Rds z{`gy5VAvR8Xw@E5>U2!AIQxQnuV|uoRC>PzWM@U;`~O5q5R1k3UuE(=bX-a`Rv%+= zsCd_3EWse~GE~>O`VvgL@%?+WmIOQn>b0ReQZ#Q zr;KFN#afz5QXTfms-3WDD7tAsRb6|kI}2vl@8MwNouB$-6-^$aUZ7#U7*k98k_&c` zqS_AFbm45Mh>00Ict+nx(M?Rzvcm4KCS9nkYfiLr#@9mPq;)^8=wb{Pgb`1CUS&_O z7|CNfG<-WCh~1sP7dEGYzh&FQS4YyMy&)R1(cs$+8h2@)NDF-z>CrIZS`Q$qIwnf% zI3FIIxm9G#Whl0S1R106oEMA=9i=D!`OMqsLl@Ja>>1`@$$on z3eC`F8^&3!z4{xLuO$?BymQ@Pm6C=#Qn7>%A{n-4Tc>~Pac8ZQ<%)cn1{%3JD5q;u z>+J>g5E%BW(fd?cSS@e0#|)#`EJm;y`8B3?$pY<22SNg_X>NuaXxLtllyNjjIlMAz z1bp9e_Kx#RruN!jP{e2t6a6gEctMb+mb0g}BB{0$e5BPfdyG+_DzYFA z=?Zcd^~#z>sxtHdG;~j&miv?Ose>l0I?4QWZEN?3|VJq$w!p0qTXVjam6=80{fntCa37$$;1{ z1;v(`uxftn~BCRKz+pGK@}GW6uEF?`W#? zAlIIkD;KS<=gFXKGe=SU@fA16Wd z08V%p2DB5NqWv`!f;De!c->vuCCS3Ft*Q-jpnq)Z#9>@?*My7C-j&*#Yq?KFpK{4r za9KD>lGrc7NSa5HueS$hrrtBPN~*p@DLeC1zReHwP9uDpZItRs$q;WPCHd2?c?JtV zPEOM`ziIkLUlmPbbL@z7TKYbM^AaxBdmKt>9~Z@37ge#dUe zi!D3db*Y}**dN{}2{rJcM*Efapec)Qer#a*Y!_VI{u{!TxjUXIAFsnu6SV(8A^%We z11hxEieT1YFidKB9R_1QJ?b2>tE~W~b}IK$I-M9E!+^!`wM{hVV11Dn!FVdCB*!kT zMH{2z;E!~ICsmC{Y!)T?gG|`Fsu2I6F>5rY!krp~vKU98>8C0c1nokkF z(;VJIpX84e&HBh#O1D_zhgn_cOZd;+qyN&a_Gn?z5kJT{!*ob@u9H%-3A z`!i(~j*1pW5=!knY*BdPIFMdR#MIZCd1FrOsT&ZKj}OcztJO(pA7+esa*}4u>kkK@ z^7dMw@g1>25T^@LHX2G2efZ{V9A%#yD#AgG*Jjw1Fs$enz2{4yd#$u1OQK!Ut7Dym^1@?8^>V_SrHkWu#G2&hpUCiPvXhqT-hqA;wHmZA* z%_4~^_t>~cqwP`SUk|y1W6hd72tuu<1+Mdiv%DCA@BxEB;_N9zI@oDt4tBB+^6j+r zzflU9*Bz21&&LNGW`Sx@ny`9SW)ks@sU^(ZjS_GS*zAl32AA#vtqcYjiT!d(;s%L; z1iO|#f2x?PT_a4rl?(1U)Fp{S(33D4ZAMMd^M>A6&l$ouKDAyOt2CqXai_?hcdLr$UKU(Qz;1vN~q7>>l2iOeIf2H`<;?`pAz zkr%{HoY()l7}2fa+t<+s1uS0OYw?co=a63y|RRP`7u|0+}S|O@MM3*lIMz1zyO~p@Ix}v5q~KaKYY8Gur`H>IZuP zoN6=JzJ!=Z9;9mCO)E*h+ucETp(r|6+yK?>+IQFk9!C}VePLDG`Hhh=_{LC&T_!sgrPYQSzJswR^@fj90YZ1j7bp=wFYcM&*CWRfl{ zvTxrUw0&&33vZ+kpfvJ4T4(o?P&aw%0iO0ZtS0sA?d7qgrpuU6IG7wD#ovkXdN1?K zi&(LmCb$Wu#eO7@&Sx*E!bqUEpB;gct~fF*mhJ4PU>q)S4D`j1(7yQ4!TqnJghU?< zC~Yp6uNChtCL{o@3!aAi)W>WeF=dP=SVTl-GIWPxJd z^1R8>hz4=O%lmR3hmj)2;Rq}YFA#dKy}GBE5=5B%ezwnDT!kVpu}cw}Z?-{V-OXZs)EA`A#E!+Nl*lwCOi$ zi;o2k-hji*=drFUioKKi8RZuXY*9RK%$)M6m{pZYqR3g?@@A}%5uL~T0AUyR!ir4X zSi^x28RP&8BGEX^pf`1Ku{M|Q4NKC?3T3_G z2(#v(b$Tuw7V(}_T)t5MLT2G(-kSdwdN1ed`n*4>PaHO?FQw5gz;;bpIUntJ4Rk;Q zVQQyU3megSw1sUEXt7y~76ZS-%0l9Crp_g=g{i(@3w(M|wH>2jqu$S>fzHQ(1EkFi zw5eMC#lRd^yE99kGS0qS7##E$XS3Ha$G3dN~*F@?|PGuO@ec zECaSEBX?s;HL;N>!hAZQ|92LJgxL@E95kiN*sPv|ei77}f6Nh9)s?u5m+A7&OQly+ zxuh=1hlXxXeP%bY9?Dx%9JA_bW-{AsovM0Tqmd?p06+uM4_zpKl{XEQ`D$KhNlaHzM{nWzHW=qQeI_Q7;*r`nPk&Q_ zjqM|(Hr)y^WkJbn>r~kW#G35kuP#h*73@*LikL@^n4Hjgk0$@A!|&0l*)k_CBrJDt@^JB_0o7Vp=XZ;}x~#z0PU zYSoTMx=LFbho|s4#&GdKv8KI`i?{Yyp&vgSWgyh&7j3DtJ06*^YjaUtx#@6K4xQ(| zawf;z`Il#ebBW2P&b^cM@XfvZE-5SNCtD>SS`V$wlGPjKS!bJfR%+=M;q5es8eOs2 zl?Oa^N|?kANZG-LFTL1?#~hLq{ukv?;7JHYvqBHplTA#_?uA1H=a;-0PC@Y%HaF~H zLt3*q%vnU>ZS?EY_Le{D$`2R15WC&m7}!c+F&-mk3P=!IIqv(bVLpKR?Htf?jreGW zniWaGA&4>byb%G6;(BcQ&7z_8pPX{z(Y(VR^5x%655A_gSj$059BN21o0o`f)Mska zqmX!u!59M(T+0D2L=c$odJ*3g0gf-!*mck}XJ$}sYa!yP#p`H!PQ+h81>*Ab6}N2J z+2R4Q4q1c!O_&B6Fr`|*EN+(2{(#B~Mm#>2-T!u2{=t3~_Kg%rJyCb&7jqDuN8Ki? zRuhKfS4)WtkeZaIbGjZPWJ*WAgipIJlxm5Iiwoub4;uD zrvFUWK=!`9>cts@W+#Ho=YuaKQ90$_%0Uun83pGG;#Tx|Y&+{#8&8TRLqPKND6+Ho7 zky5S| zmK4uYD|}F>$c$tkaKwPQw+I0h&LNlGLvtu#?gN`ClednRKW;*=5#@Qw$K)nCYxMeU>52k9Ty z?GukB(oX-L;uSVB{Vk;#KwZhctBqhWUT9b@nb@t;yL7&1u`^(H@ux~c%wV)jDx}yo zX|ikKAa6`mSu-2y|7vD>XktAIq|QLp|E50jgfEBXvHTM#%HE|;j`o>x*|%SnuU+Y9 zyB8XlptDtDRMTY+`4l3?#iW>oCS4CIZodJ4T40&7OA=;@8DI$MmHvu(l+Jc+TMwh> zKd=uP&i}F(-H@e4u=Wmb5Hyi2pc|+@bI-^mt4Bv#HNT*>!nR!~js2QTOeh}+cgal| z3wNO`{aMfZ&d4jTR4{hVykFU8sZNd>|9Bu|M7g*VK8wQnAhvR#f*r3Qrj;3YL^m&j zMZxf?oQ4$>>8tK!8-CuZ=c)-7dzO&LwJGZ-)_zdfCbq(FruVJ?T~~qTXIqq>*0QA) zGV}q*f=E1U7G1BQNyMV+`giB>hCA{LQQ)e!=`4w5@ zGW?$b)rkakeTPoQ@;Y@NOYIh<Ms ze?Sy_)ZYm#mt)A>e7a^{1&eYbB~r!UBJ(@!$4Dkog3=f#`CMkB;$tic*$0#T$YQH> z+%6oFXbN3&i6H1`?a-tb+5;7Xr-Rp(c>EgJs7Ho`U*v03bs^|aBzzt1v%vGGZQ;tx zgG-B7@32oV^My?qS=^KhT3*OtIOqpBUJWKy4vx2o{~KM0Fh{VjYuA=N$9JRW0XNa_ zYqNQwR%oY!1|#Jd)h=Xbk471QxCZ>#AKOpEe$6voF$qQclqdm8 z%&+xXFNhaK*9|e`%1h)dApRX0zlWe&Eu*rzucExxvLov6;nlSm<0wJ+qNVrP(ri3l z1JZ)(UUF-ep6m}kveJ4}lVngJa8i2gxRAI5J7r=J+@iJ6M;o?7{jd9Hs6r&-j*H`_N75tx8!>7H+f@2mQN;aI zNL1v9KV)VGuT!t)Tdk2ukCjQJ4+J@6HS18tT6ozma@+pWL_f+qC?5|HPT5jLo+(lN zs`Lh&#Q&Am*-4(LZ7Pray*8}cS-Eo5G|e8l{=wJE9Z@Mu)W7JH=WjvhHBMPK!(GW? z>doQQ~{;?MJyrCqa6dCiAhAI%Y$Q0D8Wg6Q?@SS##_~ETpn#0zt=f zGu;`-X|zbu#Xf38cg@kL6oa+&l|LbHopLbu;~;OC=(7 zQ#9L&ci(4tFu?WWEzPRwsOg7t!YfVZ;@|g^yoI~a`MBOj;-O}~qw@LLTQxTA-9mST zf-nn5{ttu^&INNfV#|`^{g*TUrOs>I*V%Wh+9BvNi@^hrx`0!@SZb@H8BvtWWZg*8 z#YGrHr@IO%V(NV8YBI$59WZPx>w9#*g@WQRzpo(Nj&wD=*B903NvX8t`EmkS(=epw9K{{_gzPx+`mkvYHg zRj<`^o%{@WC9N7u~_{8R<`+g4!cFGQ=KCg^>>&=K$wK+=>STa z^xvq(A8-Pqy#k?4WfCDUu^P5Ozb18mUlB_?)C=3V`JIo-2HP(${}_zt(Tz^mO7oLf zzP&}co0#cDrq%Y@yaI}u1k)NI+l!nQ7XsPU^l9Dxc8=9f{u5C{Aiv2>OCv*rPy<=z zm!0AI(@xmKv<-pO6=L1j`gs1GOEcX4UCYk1L{h`#l@1jSE*!q#sdvz6CCGrhQIEZr zS&|6Oh^|S^68lc(b1ebNLCD~zwk=Hf^uIa|`ga`%M-nXkU*qxTU-`Zul1pfpIwSu)tKsOitScy`iqCwKn4BvEN zT+h2_z~CZ5hGeF@^d~$Hy>J2Ust#Wfo%_+Y*;%0uHWwrGz#FGABs__2HPaoojoau~ zXf-S!)HZ6A35fs+Z1H#ICmHBwOOHmO&EXZ}a*uJ6E*Rj(S5^vEHq?V#&2>2dPAbrb zyvASh76Ma49Pw=xD)RqrE2sxfFsUfI#bfUfJD@~rO%=wvlM}UTtr8gG_h|*?dH`Z( z0y13QS{FgtiS4)~H^y-G{Z) zKPPC&o|mcZL}~Bv5|$mc_jTzu!ivfMRfvO-nnxlw?``*g4bb1KsPy-<$5CVGINHCL zPitu-*?{j!-eyHe3DV}G)H7_vZ(jZ>!(PvB4)^tqc&HcVX90T&3oECU-Hz&ye4VEf zHx0+m6-7?^Uv#b(?*Ze6L<+W7*d;N$Zt*ts%gOrXe7o2i083uwQ)C-PKL>VF>c_s7VZjAXcqPn>8B zCcXdA11bGFjjhhKrH*2nQ9K015gw9>sqQ3!J!AI6ym^}W)OSK&%z;ZnTjON|8n7H| zCs%F{Rz1?H(c$FfM@ktD_oVw_?r_iv=o2DJlk9v9u2mLEv#_ybtfj&&Km3?FiuowvfwS99H-33!f+g)!YT zUew~xLz?)<8>Deu*d%N_KRad(481Hr5k+I0>1aPpZ3CuX9+Y)lMsr&DNYpOQ+;DKB z`D5!EtH=eMZa0b;J1(&SgT85*k~uymWeo>>1d{SRNNxrv@mSdp{fWs{&UHzG7QJax z&WCdQ`a+A8V2}}70=e*KcJ_TuHw_@6^p;`eaMOpfyFucTuw?9Pzm`@E@rsV!Ro_X-@w{79qi*o)ywv{`H76*f?&7vz4b@|j8ENX%o=v3@US%X^sZhD`~kDhybrrvSj47V|n^x-tyHRHKa4WF|Q>1Z7>U(TED z(5J)2yD^#_H`1=d_e}yzo_-UM_GR9&mm~YWL?l%3m3*aAg^r!OP#GLuBQv2&Ax`UOPnZ27L zGkvXAM=CyowwGLHQqY0eU8&*|bLFFpi)H^%3PHBy^X#;Vdg&$oI;{LUg6 zrE#k&@aF{xYoj>k_++U2M)(25RsSht0)#IvB?Oa6WZ0YxM!zgG=PW?;QS(^C!7bO8 z49%H?Y9d%mrD7Y*;=+6E@0RWvRGs~{XRPfN6kkpzc&9;4uSuwfeRd7?*uf=21F|(I zc}GI`IUBOp&-_eIuB|_lN&zQB{1)HwSoe#}?6160KfYSsH^lM#(qshwkZ|EBk2Wym zV66DK%Fm`fo|dH2d4kWE{`cb{&zavX#7-J94la3h2Hso=Wa@^2P+)GvaqLCB{pT(7 zY^8Sbfp;}fbd$#gK>egE5if=#-npZ)Ji6g$i3jE$9#|Kaqw*#PtBfzmBj;{CMlv#C zKL6R}w|+FE#xaW7A+@353cI7$%HgACp+s{*@2__r69#}g7P!J>fx+UHspLLvr>gs2a=byh{`J^Az@+~@oU83Z*+Yw%;(2{_M{_XK~`RbUXlq6K*-4$Z}uydWnw z$J&8uZNR}U-03^bXF<(egGjuL;8*|C|89`x;v-5|rrqi+lxMuc2`r|t{6r9cjR@zp z4mGty)RQ>3qw^5ooPxluAY9_|vOa6Y@3?6`T_hw&I9bu~xXwwFS-Nl)1}4(X@|B+Q z)vvk3!=2mK%wc@$VVa#-$~D5hKnX1|f8ir3f(3mS&FM%T-o%;a5cV4aOk&M2Ce#LhhF2WUMIl4a(0P?v{AI8X za9=-p7X--AbI}lq5r&Suq}_Mj#3~nD+r!+w0W#$}hHt&UL7Zy=J|Z*DQ09J=5I{@1 zWoJd$v*=4vF0jFcX`JI(g5F#UT!|Qoj-dV@|IZZ(^IA4-* zXj;bdh*{_)<+)*F*8=w1yEmxjdwgF47qYb9uad4WPIymXbd^yeKGLe<`c?Cnbe(}b z`x@2C8unkz?9qbTJM3PUq=1Wus*lG+o@AyUYTrAdm5guHj5Ly_P>sX0z2a1al7Hyo z+^)*0@q6-m#tV#g_+$C_BDr1mYMFowUvvihg8gmiGvVmyw|-Nxi0CVm`Hn66;Ui9N z_@7Mg?NJS4z~pMrjL#^v=$l1A1Ho|Qv;0jLxx{SpZyzS1xDWwM{{xgv?Zclp%6Z*W z#iTc|<=jxDPJAd$K}z#tn4v>=P|s&>LhXD1aFU1*U&X}03FGRu%aT@);YKCRoBbpw z1p=tPcb8T(hL3gLa7Q&HEBB#UIUv^HU~D`hbE(iyN%k?9T-gmQiBG>%FE!x3K#sLM z1?%eErbwZmc(#+1vc7MPuwe42TUABlOF?Jx44DXN5 zPvU0P+0_g8#dMRLd-tuV6Ax9J5GX7aiU8F5XEtcBpYF}mnfj3K++U3{Df^NLD0tp; z3nxBDey@SIwMRTdw`yA{JU^%w+wZbr#`|?R@h#bH_7M%F`0g6V4#%It1DW%Lrujd6 z`Hm&-mm_aA6FIgRZk~FPph$Tq_)>ggXMDDHaoArrvG0MW^nga3E z*e~=C_b+LR-aF)jg?rxvC2->T_@V}`H-34@CEqnR9$9CSl@#|y99R0i#FS_vj-yPj zJvCh)SPLd)yANW|zUNP_JsIs9c%bV{*z0=in!U6ibFG)zg1PXF<& z-09>d6zd}{^hU}B-rRnHt6NO>kpt<%THF`HBSd8`*JqNMg4&`9|Cj7m)va}Dr|hPV zr!40TAI5tw(aaY#mkRt}`M<_D^b2?=2`5Qk78FGZ!;gwsh)TE`yh@@D;GMVZ zU^%Gp(gwxXm}Hi3+XVO>7QbMswO=bjp7MT0TuKu2UX{o=s*P4d@5MK*_R~ZMV3Q|D z(e?^U6>pwWEPUR&#=#-WtDh#6UMyRHG1{|Nyym!uM#?*h4=1WzIb;u1g=WKjP$;AH z_}M0NAH4dW5sj;#_w)}KIEl9OK3rq61Ed!pK83IFsA+Y78UUwk&$6OBos3ZU)yav* zvs|fK#C*mH_lRt6Bma2}>{ehNFw_&Y9SM|EvX^=yJ?a4J5ylArs%pnU@|l?&#P^By zWT4$Pueg2zs7(~nzeGWK2vlCl?&kZx0x#w9n~XW# z3Cx)Tos@k2zMFHT3RLAVVV!G`A{+Z}Hf(t3_S>AR#ij;Xz3mq-`b)C}vFAVvpYhT+A=xyF8seI04 zI+1255Q&;y;Ug5t0&;LIF7g{^Vp;wx89mjTdaBg)OyC~^@3MsQJICPVYn!#Zqp|E5 zGXp$PXt^X>>DDsuX^-HP(%c1NMhX*ybJSj_XcMj!Xnccb0*l>AS7P(Q_w1LR;{-}L zSNR}re9G%xp5py!Apc}vW*1+Va0h16{v!`?&FCk=7ghJBSH6Y@u@H;d*HyZI7{0oV z1@3fVxTuaM^TLgy`Mnr0CtE7XY+Gn(H};+FpyKRokQt^Qn{)H}-axI;DSHSai>@bj zwV{ZdtpeB7j5p5?&x%TzjyedRylvOi>o_L#HGKkquVVB6?i%rm3&-6!B(V6JM_EHS zj?=P}a!~Zv1`m3=2#s>h3Nd*G`08fL@)v0te?{l**EQ@H{#4&aR*K<=_rxKhp@p>siD_Dt;f|6FY;(cY?EKfQShxkGuRo;mc!C$2o5!v^k z4xwZ=1|Ze6fg#M0LZ~{YU`ejq;u9RRK_FOQPE2VqFGlo~AN31<-2)#5?phnHI7@9HH77T>CKJE!n)6CO zf>qPp8O7uW?{VSXv%LqUYEe*h#Q1$%U>Z9D$n+$4Ef(yh_UwIrmUMLwb>t7^-`7v7 z^f+z3B(DF{D>UN6@Eco7j_IYFmawSb2;R3ShJ5%TYHGre#y=8f+i7TA`TIrjTT2iU?VcY`LMrxNIS%GOWn3~ zB|L@Pd4~B}N)0vLcT1Y(DYa6kST@(SHmM5jMLHH?R;PVlC55vgCNNTp5liX^k6cn5 zWhg(XZc7U7tHK8(?BW8~aHvO#nYqhJGMLW4% z3gq7~-@NxhqxHoe%)gmq6knv%JtnXz-5ACi`-!Y(r{zTmVgp(hcrHUCz{*_^?v_Vp z*vN($Q#+YA;Zb$wltdfHq(1g(;P2%T{@-tcg+I>h(uT$=-aY=R9jx|=IvsXO7B$II zpStjAv<@&SE=|4zWgDzuF)S*))_mRj=;77=Go9(~3FoMXJCX|JND*ouJw40rA*iw^#CUKfs|c zIyn@Hx^H=fb~ol&;Ud;{@`x#K%r~i)swF-oBPuhA?ph2KCw6iVniKG2uiXLYWBooi zpv797xw6>3u}z<@7UL^N#Esv}^I3P7pG*60|LO49>06O6l~8ziqZ^y{bCu1}#yaW& zk-6)=+f6eFAKy;WS-r8p<(M`m_fJ}3K0vdw*K**azcSyJVHN5gWR87`i1bQv$jvMU zyM6^AaCtaRhgVZF$i3T0?k4jYD?Tm{FeqzfvgHVOOJCjAvU>kb<%tC~C5d8#3+ez! z?E;oe;lo+t(ryS$9a~&^AR5Qgxxvc_={#t;ta{5;`AqkCP?K3c@lnC*Br|XrvF^L! z+Iy)Po2q5MkzJ$wzbi;l(10(7EupgB)C2Al%nw~+tLkKeh}WSCdkR+fVZ9+E`s>Wt z_#N+liLDP+FngLpc0f?@8vG^1!mQ3F22BaktSa_PH-Ca|5Dn1~|PT zvcrQ%bLJOP*m!B}tNu9$bcM=_HQH_sZ0p%5qG~g;D|cFypceVPTte6=jy$gVBL`1aTd5zkp1wywmk0$l9Hiu| z@eE~GJkO9smgDOJIC?HLoKbOL&j=i1y_meRv2MFzO0IjifiVoQA9hCdA+Wxt1EhnfwR1d#3J%2&Lx@RCkW+&adZeY6#rFr z-lgI_^o_1+CNL2$r_YtAIB;JUhQGG7UTRsM9i*Eamw%eL7>rUkHKrV&*)^F1$zD z1lQlLK|DS%4pUNY!Cp+E1#BB|`t@*F^)Hey1^vx6t9q!-!&kyAKQd2~1>tlr-Qs=i zeUH0$ICsxZ!+K7|B)ZBO@7p+;!M!ck@H8QR`Yt3Dm}b18nNf@sIp#C9?==H`S^N~^ zMl9h~Itj{+7t}$%?13t#TVon;8rb(ZAftyru8!tv*_f!xXO^eGuH+afFvW4bTQMen zNxjqbDJ@~e!@tjkY-`DAw`zOEZY5?epKOZge{r~`Hh^@*->1Gy%t%dtL(h*nxR_M+ zG=ag+atLC)IJuI#bWwogCgz|qifer&R=fvJp_NC8b0&6c84T4<R=MU{U;uGauIfJT zWd}0c^R*y#U)?&2nXA5>jyPRihYWCuPwp5mEC{e>0+UqcDHOivq!w^Ljv zKbpGAKNmSCt2P8>^BZ4%S9rHh|5>JNh_d&b6p$0Skt~_xoLX1-tB;MU+~?u;dalW$ zgtI+vhSj|zp1tXG|Rlq>lMuuj5y8R7RcwE$ADmX0}|y zM>ltL@@@Pr2TA7Ol6#4GQ#(8 zu|rpv&xiW1 zeg63#IGFb%WJL>n)HQ(8Jv}&vn~-go_aoXfc3x`g^2N$d%?5a$;P_)=LW(%`yc5Yq z3<2zQ-f`lO>~K^S#e@?X;bZ7F*xfx3Tq9_C1KI}S({YLZd;qT18@+dDKGK`ZPVD3z zJZ-y3a4GqsyVaiOB;{deRy}EkU#4*{u@R+p*Nw3ceo|`ZD_$vqGT{Sk9 zPKyUz1Apw_#tnXmkM^4Up5IL|YPaf*ibGNnf*@RSVI7>vDeTUccbkqCUY*#=neU_` zAAj|VrM+$|D+BnldM4i5Ugp;wuTq&(rmY5?$U$>?p!`L5Aky(u+Zh2|zn&qzn4v4R znu@6gY4Ocb$7L4Hw@rTa666>4XK(po*LGCr`}sOGb7{F1Md2tHorDG0-hTw5uZ8_c*r)TbdGnxwqV`wTaw#}vS&HTnxwgM2_#(m?L^ z3kMfpjaAyKm)A)*?r^YYh0Q1~V!O`)ElglHtnGo*pn!An>9b?T1WWGA z)ry#ek4eAQe8^;e77e3v*#;J%o*%s${lY>_pQih*>-V99v)0zS+6K7q9~RUBuWMQ% z)LY|1OF6$J(_bI~%O5$5r&fO>HBRwf1hlik#mIfr*_(v!i|SqiRXrfyRYarE{CLu; z5Mj=>mbK@5j$i8Z!R~-fY)Z-F2!H%(Hq#oOY?QTG=wuPo8iX1i_#Z-+N;0Y=p4}1hItd))ON25L}xj&L9N#6={_8Y z^Qa1qMf|gG!e96n{<0oNIJuRR*3Q}WiAES5J&XG2Cd;3jU_fa^(a0T;Y;p%9b- zjQUAY&M+JLW&KvGap7!d?|JHbAZbC(_R^pnWh0h?62M;26iMn`Y%>aAU5SNSV9_a0Jn8t;H{1>oF0r!!tV zGCG}y-L}j51M)&cDWESEu&Yy!9tfAUy%gnsGc5g}OitQFk7m3%f^Fhj^*LC(l!}bc zI9Gs@Ng4O(pXRGPFD&p>_x-zg86y9%&>f{Bjvw_K-F6l2=1g$3d%VMIk8Xwj;ha@l z8`yleCHn8$eUb~O?|d*z7~7wj$t9>Mqe2-kFUtod&wN*r{c_>eV;lOa`&m0F1Cb`W zyZWbFeJ^#&FEkz|DEKEl>tW_h2Ca=n)Co!m7r$i~<7p`Ds3ty zvT)+BaU&jj>JVlW31qp;xIX_P8xzaLZk78=m_Wn~B`$ZQ<9CL4i)pHGE~l#m=gq8x z!~2h^{YRf#*eA5L>(zD)1Kv~_Db-LYB;9LG7_b~E^0g-=zEd(gt1HNVaR$&P>};u z^moopEelOOG-5Cg)&Ly}_8W%Vwh{QLpIUagz1Rx>%Am#T0fWJVU*&(YG_};d_}tXy z<^slZT3Og_B(Ug_#BKn7KU}P7v-=RwFxpO2gY5-=UU=*^UNn`!203oXUcTX%@{!ed zpKA5tf`;96L*9mZQJ$9U$yGZRyfY{}jk{-<0Qg;C8e<*7N|300aOTdd-{X936;J5h z%Sf;b;Xu*DcnDjdcEhVU$xA}Ne-QML-GAy&mt@Uw}kzeGmd44T#PAz|}p6jlmf4{BbHb1eD zE+m!G=x=?{%)R@)75PePk6yn5TWep7VZJObEW+;bAor7yLc6V-c|^pYac{Cu<9#&X z!ZSLFIKo)KKF7dHLbI;|*M*(R$A>y#G@^Eebp#|zGC;9Sr759fFi#?kZSEiipFn|k z^l1hAcJn_*VLldof0!AhL!8T$8SudLL5ZChL@C9It&D^hWx-0DR-Y6hH-|s3R?sejD$>fCg#dP3GQu)SeT(r zi^zO9Pj;4UvPj7Rvxwv5>|72EZ+X%cXs?o385^k*jMroT#b0}GjdtX?B--c8fiTg1 zrbS5jlkkx?G*PlDQ48K5oTyjJ$!@>6P!OYs4{4Eql1PBB4PG2=%bmoaDPD@w++V9w zx1aZ*aEr}nVk9gH#EsR9yxSV&HjV}P>27oih(~w%j0$J(l%mQz`$k(R7HH1vk+2AH ze5Gug`|{kT5%t2>uX}&|xbMfAwLcqvcEu|~^_T~kcZS~%Aj+7r$$b*iZX>mM1L{p^%lJPXM4S2ct>#OoE_u>f5D z5bs)uIL1-NdT<50H@SSO#n5EVpwtrwjmF&y)CRas)qoE2#y|Ys9v3lq+Ew|Ny~@Ec zxWxr>?=}q`oX{369KUy-FiILFS%+6A`uIG-a8UWe^TwGv>{Q5&D^*UnF0y4=|L&q= zy9bw1M~*^;w_)cE-WaGO!UX7>ZkdHtCc z)jfx3!#-(QI27#lnS+6*8Dmn-lrKJ*FNySmv74b;9x73_j4w*$io&g(f4S!dep$Lj zhUKHMAV^h&xheRDE;oE+_#$3u<)pn?$)tPl2sd#}_NP3HED(ExJ)K()Lsr-O{m(YM zX*V6|^0LPfk0z~*sK8M8VZ?qdNUaFr*kx~ZKqI;K6~p2Sx&U) zs9ZSR=@j%UdQ^pwi#F_jbl()@9?=4RvZoHum|Iom|9HWD9K-13*FR2Mr$X`)s+;f( zU0p@utdoCo3p0>o6b6e+`BCKG2QNxh&&=L6bTPmo?IVQEaU>g zovH38X)Do;Bk}dWhYb(;eQ$4R2oG#ySuC3*KnXPk8d`wFUdj2&RY|2yi6x*6>&pf{G4LvXMi6JdjaR_ zx=j47vDSp<(fBastR%h>)syBm&VJ>u{F2Xz%lKJ_A|67Q*hy5@4AG<|FVTfMIWvXH zN~1bsOlmUPZ(92WN?=}g_AChusJzav`iXd0`+SWyFlncKgE7%bP72*t8{@puz7FFm z1~IU_!!5o8K$0MVcn0$pZ;9fea8#PuFXy|78Y9M?JJQz4e)SX{jpD+6R>Dtw^kPbE_Pt02rMU1C55Q|M z$`(vUttHP7YJVwtx5A;>A3a}6*gH9|9Oy$;GNG~H1aF%wP)%6l_P~RD^qYhC+78{e zXQoK=(GI)jl9Cv(s@g-SX<-pHXA@3Bfy&OAgO|+h#;|zBM&oME-SSy2NaGW$0}=CO zE@SbR@FKFuS+LqzJnQfEJWygxeAQF5uB&`g{MR<_IZO6zNAHx)hcbYwWkc=aEDSG0c!+xpr0;QTKN!WSv9Keg-CWO? z2r_g!98Y^ZSBX6=XyzT8Fz1wwBOnoT3CZu9~B#Z8qXN2xrfm`m8Q9x`;*4sZc`_zibD8d zkw70-8|VJxRlTI{^A{0EHOc%nyw~&>2308Yn@iF~o1iz^LO;BNWF;bLL+$Lx^nWQJ z3@%R{hX)YjJidKPVm0h)Ur130o-W!Pk>c_{F@@_awj2Z!pZ! zt#=2_iR!)QPZ(^8-+m<+uSi|*hi)904-C-)fpJzJPHw*60ZC9l_I~D)Q zXaKNceV(G8olbUl+*t!HdovcQw$4A#*>biju(ho`nZ`}$a1!Y#-uH#Dy3MU1*y5ad z_e`)+GC5U}FF84RY(xG?UQ|aVSCe-q==n2fR!@psF6GWWT&0)Hp~xN}A#Bev6^by1 zMHQK-=vvsq#3Di>H%Ul9&1wS&Pnl7tyLIcvb-2?eb{x5k6uv1+Xt+}R?ba-v+fU5D zL$`w=So5~X7#pIz9VRXL(PfnIy(!X0(At48S0-B@rF&|DJ+D+7cCcqGzq2s87T&3b zx8Rtp0cI-{8)Hl^KW(baPvKPXA(=kSJ(eP)P9%jsa@+Dg_vDRw;6uH8=A4CQ!3Ov0 zOF*y_J7wik&%0v{?rZ_cQD|QQ#5D6`WV&12_@=cE-jc^*%|x@YxezriJVW3#Rk(0R z)5?s7YCgv)+0UizXM+j5l!?2~ABsQUWcT(rqN1X8+j*-fiwq^WrPkXoTCWJePl-_RPaR@P>(5G|>OM10uL1}x__l2aav2fBlHn+>-)k=ss(|>wrNkzQ z%S8gmpdnMm))Uee>PqdV)ZZ2BOTJyL=%;v>`Q2B3Bj#fGHxs1V$-t9}L#EejXoc(; zfRh^3IvLKZXR1k01t`Q^RH#PE8M$z?c|?Q$q@TitxB`cy z%VO(;9iXdojX!@EyjH8I*EODFdgkJKK+%V$zKW%9N%wQnnyoq_cb|}BEl}xwg+|DG z1B8Zaq1?3RYrUT|Si_PC!(Td2wA?SX-*rcO>lV3niM#Pe<$*hZOz%b6#Z6X6Cs zF6lp~YaK3={&65!Fo{4{8LiWk8l{Y>u_I5!5%4J}UHMeM=iw&C3|VB$j)f(9U2JKp zuBNcS&_H@gw60#0tZZ0!&Hd!gw4cEDwOBMe5)TWez)eb`zl_Jc(WocpJ-(|nRC9clb)9!jm~8`^Qi7VG6&wc z3i=&Qw$h{H3nHSd{_{KoY5nKVTd(3eZ0C7i?KHj$YQH<@;tZt5ZEycJDg|tg`Kt~H z(@+Qs7GYZD+mEY5A8RpL&k`?bd)H+$_r>zHHs_mcW+<Xs)^m%flPny`qb?pT+4LoB`MQ`ea+6eF=R7ijHX;4^p6 zEO`aXEjWSIw!n_!5f2R?_y64K<)YNu4NB>SUzD{-0&dRk0BdlQ(a+_}&$+s+b#+a< zkOMgvItX6Ut&vUgPEuCJ)DNJ6UL$EF{Nkj!xZf|nn(!L4e(LL3sPs3q5B{+vB022=_`jBu}TBrzEu#gf8YBZGb)MOr|yIeDqn?Q$$O0ph@zSh1Z5 zg$ucJcYD#DTJ0{dg~Tc{ByosfG07~lS~(dkb_OtP-23hQ25+g%p)%=HJzB4~;@D_Y8{(LE9YS3Ro`Xy^F{5DkhqWqnq{{x9F`xErGc!w`@ zZ%80Zi{k`HN7ZO({V!8u3=`rtD(zFFGqr+mWUEnTEQk?`<#UJyrvu1;Ka0 zwDlvKZ4IyCaj=PsXK^L_8jr$XWXV%_wKQPyk9+**x8f&OPrt}yJNj-ckdkz-LuA*| z>nm1tiA)Zvs7;RFMSv|#W%VF3!ED|lv<4>Yw;R+%=CEV}%u8Uag-s;wS!at^ zLQ3}IR%V`X(Z-^n5yifeT?9W1zi&@dK`)}5*fQ!_(CD<*Gs|Hh3E*t;Z#pAeVe+Z{7Ger%VUTe<$LyLg;d%&}cA|I#SzWWYgu<)8 zyLg7!MP|p3mw%o7KfB;@Mx!7zu%JhFCKa9w*?bva*-o}GN7mrrlL6egnz?G&x>gSr zrahJwIpRDLTH6g$v(soXg#9MfLL#Ug`l(N{w-@xgE@PO=!w`;#)hrkiEb)bCMLAXH zCR8z@Xi*DQ>YHVtgXu?>x6fotRi&4eb8=r~6~2J75c7A39(P~nCBTI4oPZn2{Qz zhd42^)m~wp&L{RJX!++h>ReRQE;+qurE`>cw>hhtu=BE<@BYi|Fek>rH{lzgH5QLp zicQFZEP(scpi!1tW}9VC06n+eEmVIppCi!kwWV5XXXw=Y)M^3=Ep^6}<1=@wocs2+ z+oPCIC9q?r-NHgMpl5ze6Adr5&^!00-);Fm zF(GndbRzHs2w;L740x7jEIneAP5F~>w1V)%Nvp|!u&Uh`6rY=J&9$?|NgpumE9gkY znFNa>@?P`xgrQ8K;>Zohsg>-#`rdU$?vC9nthL$x-Kyehv+n}#A%RBW=vT{Ao0hg4 zYxI?8V;bF)ET=2D$BXcV@P(No!Xce1^KkFKBQ{zl;J>vdtgseCC2)sg=q zXxZA09&gNC^(PjI711A@y=tRB+jyv%%5>gUlQfmLbz0(7)j-Ie%}=iQ0DG!h;r8sX z93Fd5OSJQx@ty9UoiQIBIz;i-089I-%JdmSYRzmHlcnPZA~v>Sb`+-Z7s$_FJceg0wD_)$b_P~? zf-rC)ow1HQL$mBErYO3r^ftvnlfGw1moe&so8d~(>|cTaA%#OUWQ@8`#gv@Y+w
#JgGDOt_bsw#l^ zojd3#MsPxGw^&WPqFUwIHmoriy;mnZUM?ihyk~rf{BGo~du__!f4{Jif{zWK>bO2K z3>V_JTQCC7v(Uv)Xk@8oLVZx(g8#i%MoQCUghO<$nHatdw}XPcYXtLiL9<`3e!$OEd*7_ z+js--2ND=x{CnbvLmy9*v7kL?3pHxXbZU2Icv?FO%Jtf`4-d8jPfaJS`tnfyfCU$AHBZ>H~kx@FtBM)!8udV^YSq$8pPGq<&= zR4}?WV(e&bEhj}%Nb=CV5WQYHEoBRm)JdK2Hk&!J0DlesMPRSWzz^zuK1Kcm01}tt zYIAbUaO*V=;I2!|5*fU`Jcb!9jIE@IM8TAfDw*4!%pfv~J!8%W*s5Jwua{KTuJLt( zz@J*~iX};_2PhSF358ZhI^|r4&pEv^V$wsM+pkMEV-Elr#z^{i)8j(Ho9(l80Luy> z$$C^&BqIp}F2k*yW3a#rC;2@~B9=aHg8_vBhk>RKeWsQ0C=h*pZ7$?+lAuQW->|BS ziM1@Y@yR^VeLa(!jV`etfJXd@U&LB>WOEQ(=&1b42bb#Ic0>5c=Yc#$Yb7eL<)z7n zgrih3a2&;*A)WZmh`_1uGP1p!8$LS#mX11Q6?p>Ck+nP0mV-3B~!t#ynfsBLaUG)87#X&3uh;s72i5 zjQp_!jUY77wC;FCD5(bU*XXA@n4Z?XV4~B}FFZ88wpM{IvG=F{qI>k_d9P6J9;Dr3 zNR$EDtYDm@pf@-v55WS;@15cGiQLt}T?r|`^4i(on9IxQC&6rQDjg-A^nX91B}8WP zx&ze&qsfM`EuMrFfiB3mCZY=%jzU;pe&i;ceu1_R#L0t>;4I(sT>0tzyj=sjX829D zN;#6xO z@cz)-5RSzQDGdU1VbvLEacZN%6;EZh`4!A}V>+8+6;qSXYZIxqP zj?Ed>W$1!>7Ssj&xQN zI^QIjUa@^fX}inq^fr_BT#TCPwi0#ndoyc6c%WZ}em#DNQYp6`#4oefaAz9%rMD_s zQKX|VOWfUL=ZB)Vdqo?zCHx6$no|B&`XR87bJwq!g}rWD;1cAwatTv1BskMiz*)|F zno84DZ=UjGqsw3aFhbZ0>odRFE)^KCmWlDF%frFb3QjwWcx?n-22wu-G7Q%bH1>DKfqmba+^Yy^| zdu_+uXv9z<7P+C?osCN-0kw!0^hwI>B3RN*DpA>?DT0Ulb?dmT9PqEhsu&}w@J^L= zCh$MDc(s}wIonHCO>isTjnqx1LT)z^yS`Xv8)KUyb>^Poa)w_qHde)VL3gx8c#(xV z$h=Dbrz|P-=54-yo8Z3BVtx5*>XT|es;V89VpRGJ(T!$$m4uePuQd+mQ?+?HN#7Ls zI?C-X`~qF@(QoK#28#slFY_n!@ZonUW#P(4X#IKkO5X_wqpRc9)cN#WKwFhZ&d2S) zol|>R;xt{36?0lDJhefMI8eT~u+L=|igwjSfB7FS5;oLdDRDp8P#8L#ZoCDOplE-% zWrc?3Mt@QBbWD+}3%~Qmzd4C$q3%*Z@^Fk;$89|hLnA?=c_4gU-IYYz9-{Ci#20RR zr#=wgB4hgAGNhJDnkJ*!=X|p;PD}ew0IRQKa0_Wypk_5gU^q{4=NfH8VY^IS_;l+1 zyjQEb;Uhx$Yr@JV=u-8aK;Ls#HXKwd+zjGt{kS#}OX4{?raAX&cERf3g~Ckk5KXcL zryOQS09Nk4;z0%BfyMY}kVMG&YooB2_J=XGO_h=v@mE{>d&{qE&$0J@mKDGSQI!PG z(w{CFyUS6vEh#ugSAXymK$lRU-}^~Hy+mHVfGkZSq`veKTq)qbNzuI9t(n`*Ch0Ow zndbKG3ZF`nDNd=%Y`zRC$i~XhmE$%KuT#6Gj4v|ZI@a*JZ`=$uKO!ogx6U@0~?@knc@O9 zQlG17J9CEYxMuxfbkGelWb5H(q0_bAkr0sg-YQg0{DC`i*zFD^z8bO2w+>?x zy!{h1OZUZ;bz=$}iosf{2nLu8dp|IqO!m!h#$e^~X6`UnEZqqCRjdub6A95ykkvYN zBr`*6b{!P2uU{VOb}Tc%&0x7<8>S-*O+jk+*-}na43}PU3gt2F4C`Z0B$U-X33ra& z2{8J7IWR>=;&uw-h!Z-2GBpVg$e+sG9WGn-_Vju&rk@)3n1kE&5vqGAyYA$_!B7l`E-`Q2G^3Yd4#cyqVJSFcnD9yUWMQ?};-)fW4 zb8(=mB3Z?a7EJeYGlXrNm!PyR(qAqD7q;APEKbH8L`>d#7X0NoZ8BvN9zqxEQi8EW zkXb0>LugPICYrTzovR*Y;p8 zgP%NA^4_;3zrABVa@muJ%l@_v#lNX3BJ}4NpgHC^&+|4Itm6@8-s#MEZ>@!Tg>{gz z`OzY`W>?6x&yZ=mNi10_cn)KRIF?LU=P#rmWJo4|em3Cix#QT?bwm-<>_@Pu<#8+t zn^uAx+$1wMsWKba7-l#xezx8BzJH;?Apxj_wy{ob`q9@r?eBNZC41sgo8*Xn@Be0WH!OK1qyhY26xYyl z!iLBh#AQ8$c+xwT5Vr=HhMWb8+$6)^M6fO8bv{EMjx!PrXc0`nd({dc`Hh)zS$%8v zDL3FF>;w8-{}c_U3d`3wIEM~kFv?}G6Isg&0wiRl$-o4{5Mb$kwcc;$FUEL;8HZ@v zJdRe0_Gh&-Edr+CmB13}A7)3;E+b4C!KTOEA1bP`SFG z40A_MnB%r7hIIRlNVs0UsTj<6Lz+ΝqBzwlASv%`gUL^eHudY)oJied&iP!7_iG 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 C|E6~U literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts new file mode 100644 index 000000000..d2aef5da6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/lib/index.ts @@ -0,0 +1,121 @@ +/** + * 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 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 { Construct } from '@aws-cdk/core'; +import { CloudFrontToApiGateway } from '@aws-solutions-konstruk/aws-cloudfront-apigateway'; + +/** + * @summary The properties for the CloudFrontToApiGatewayToLambda Construct + */ +export interface CloudFrontToApiGatewayToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide a lambda function object as `existingLambdaObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided props to override the default props for the Lambda function. + * If `deploy` is set to true only then this property is required + * + * @default - Default props are used + */ + readonly lambdaFunctionProps?: lambda.FunctionProps + /** + * Optional user provided props to override the default props for the API Gateway. + * + * @default - Default props are used + */ + readonly apiGatewayProps?: api.LambdaRestApiProps + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly cloudFrontDistributionProps?: cloudfront.CloudFrontWebDistributionProps +} + +export class CloudFrontToApiGatewayToLambda extends Construct { + private cloudfront: cloudfront.CloudFrontWebDistribution; + private api: api.RestApi; + private fn: lambda.Function; + + /** + * @summary Constructs a new instance of the CloudFrontToApiGatewayToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CloudFrontToApiGatewayToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: CloudFrontToApiGatewayToLambdaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + this.api = defaults.RegionalLambdaRestApi(this, this.fn, props.apiGatewayProps); + + const apiCloudfront: CloudFrontToApiGateway = new CloudFrontToApiGateway(this, 'CloudFrontToApiGateway', { + existingApiGatewayObj: this.api, + cloudFrontDistributionProps: props.cloudFrontDistributionProps + }); + + this.cloudfront = apiCloudfront.cloudFrontWebDistribution(); + } + + /** + * @summary Retruns 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 Retruns 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 Retruns 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; + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json new file mode 100644 index 000000000..6b23b7d0c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/package.json @@ -0,0 +1,83 @@ +{ + "name": "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda", + "version": "0.8.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" + }, + "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.cloudfrontapigatewaylambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "cloudfrontapigatewaylambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.CloudfrontApiGatewayLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-solutions-konstruk/aws-cloudfront-apigateway": "~0.8.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-solutions-konstruk/aws-cloudfront-apigateway": "~0.8.0" + } +} 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-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap new file mode 100644 index 000000000..e9a74b4b5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/__snapshots__/test.cloudfront-apigateway-lambda.test.js.snap @@ -0,0 +1,720 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test CloudFrontToApiGatewayToLambda default params 1`] = ` +Object { + "Outputs": Object { + "testcloudfrontapigatewaylambdaRestApiEndpoint0A6CB43E": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testcloudfrontapigatewaylambdaApiAccessLogGroup97EB2E40": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudFrontDistributionCFDistribution4AF2BFE4": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudfrontLoggingBucket7F467421", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "CustomOriginConfig": Object { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": Array [ + "TLSv1.2", + ], + }, + "DomainName": Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "/", + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "://", + Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0", + }, + "/", + ], + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + "Id": "origin1", + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudfrontLoggingBucket7F467421": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "testcloudfrontapigatewaylambdaLambdaRestApiAccount1A4578BB": Object { + "DependsOn": Array [ + "testcloudfrontapigatewaylambdaRestApi96B9C695", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "testcloudfrontapigatewaylambdaLambdaRestApiCloudWatchRole7A327F48", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "testcloudfrontapigatewaylambdaLambdaRestApiCloudWatchRole7A327F48": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testcloudfrontapigatewaylambdaRestApi96B9C695": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "REGIONAL", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "testcloudfrontapigatewaylambdaRestApiANY293F8177": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "testcloudfrontapigatewaylambdaRestApi96B9C695", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "testcloudfrontapigatewaylambdaRestApiANYApiPermissionTesttestcloudfrontapigatewaylambdaRestApiB469ACF4ANY1049443E": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testcloudfrontapigatewaylambdaRestApiANYApiPermissiontestcloudfrontapigatewaylambdaRestApiB469ACF4ANY60888502": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "/", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testcloudfrontapigatewaylambdaRestApiDeployment3AAD18356ddf1e04746630aef78bc65a8edbab85": Object { + "DependsOn": Array [ + "testcloudfrontapigatewaylambdaRestApiproxyANY21F2417C", + "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30", + "testcloudfrontapigatewaylambdaRestApiANY293F8177", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "testcloudfrontapigatewaylambdaApiAccessLogGroup97EB2E40", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeployment3AAD18356ddf1e04746630aef78bc65a8edbab85", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "testcloudfrontapigatewaylambdaRestApiUsagePlan283916E7": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "Stage": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "testcloudfrontapigatewaylambdaRestApiproxyANY21F2417C": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30", + }, + "RestApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "testcloudfrontapigatewaylambdaRestApiproxyANYApiPermissionTesttestcloudfrontapigatewaylambdaRestApiB469ACF4ANYproxy3BCE2E38": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testcloudfrontapigatewaylambdaRestApiproxyANYApiPermissiontestcloudfrontapigatewaylambdaRestApiB469ACF4ANYproxyA51D7C81": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + "/", + Object { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "testcloudfrontapigatewaylambdaRestApi96B9C695", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..e6f8073ec --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.expected.json @@ -0,0 +1,717 @@ +{ + "Description": "Integration Test for aws-cloudfront-apigateway-lambda", + "Resources": { + "testcloudfrontapigatewaylambdaRestApi96B9C695": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "RestApi" + } + }, + "testcloudfrontapigatewaylambdaRestApiDeployment3AAD18356ddf1e04746630aef78bc65a8edbab85": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testcloudfrontapigatewaylambdaRestApiproxyANY21F2417C", + "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30", + "testcloudfrontapigatewaylambdaRestApiANY293F8177" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testcloudfrontapigatewaylambdaApiAccessLogGroup97EB2E40", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeployment3AAD18356ddf1e04746630aef78bc65a8edbab85" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testcloudfrontapigatewaylambdaRestApi96B9C695", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + } + } + }, + "testcloudfrontapigatewaylambdaRestApiproxyANYApiPermissiontestcloudfrontapigatewaylambdastacktestcloudfrontapigatewaylambdaRestApiEFF71B39ANYproxy384A83AC": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "/", + { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "testcloudfrontapigatewaylambdaRestApiproxyANYApiPermissionTesttestcloudfrontapigatewaylambdastacktestcloudfrontapigatewaylambdaRestApiEFF71B39ANYproxy616372B7": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "testcloudfrontapigatewaylambdaRestApiproxyANY21F2417C": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "testcloudfrontapigatewaylambdaRestApiproxyBFEB2E30" + }, + "RestApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "testcloudfrontapigatewaylambdaRestApiANYApiPermissiontestcloudfrontapigatewaylambdastacktestcloudfrontapigatewaylambdaRestApiEFF71B39ANY25A0D653": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "/", + { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0" + }, + "/*/" + ] + ] + } + } + }, + "testcloudfrontapigatewaylambdaRestApiANYApiPermissionTesttestcloudfrontapigatewaylambdastacktestcloudfrontapigatewaylambdaRestApiEFF71B39ANYAEAD59C3": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "testcloudfrontapigatewaylambdaRestApiANY293F8177": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "testcloudfrontapigatewaylambdaRestApi96B9C695", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "testcloudfrontapigatewaylambdaRestApiUsagePlan283916E7": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + "Stage": { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0" + }, + "Throttle": {} + } + ] + } + }, + "testcloudfrontapigatewaylambdaApiAccessLogGroup97EB2E40": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testcloudfrontapigatewaylambdaLambdaRestApiCloudWatchRole7A327F48": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testcloudfrontapigatewaylambdaLambdaRestApiAccount1A4578BB": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testcloudfrontapigatewaylambdaLambdaRestApiCloudWatchRole7A327F48", + "Arn" + ] + } + }, + "DependsOn": [ + "testcloudfrontapigatewaylambdaRestApi96B9C695" + ] + }, + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudfrontLoggingBucket7F467421": { + "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 CloudFront Distribution" + }, + { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudFrontDistributionCFDistribution4AF2BFE4": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "testcloudfrontapigatewaylambdaCloudFrontToApiGatewayCloudfrontLoggingBucket7F467421", + "RegionalDomainName" + ] + }, + "IncludeCookies": false + }, + "Origins": [ + { + "CustomOriginConfig": { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "://", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + } + ] + }, + "Id": "origin1" + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "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": "nodejs10.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." + } + ] + } + } + } + }, + "Outputs": { + "testcloudfrontapigatewaylambdaRestApiEndpoint0A6CB43E": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testcloudfrontapigatewaylambdaRestApi96B9C695" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testcloudfrontapigatewaylambdaRestApiDeploymentStageprod9373DCA0" + }, + "/" + ] + ] + } + } + }, + "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-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.ts new file mode 100644 index 000000000..26df23433 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/integ.no-arguments.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 { CloudFrontToApiGatewayToLambda } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-cloudfront-apigateway-lambda-stack'); +stack.templateOptions.description = 'Integration Test for aws-cloudfront-apigateway-lambda'; + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' +}; + +new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + lambdaFunctionProps: lambdaProps, + deployLambda: true +}); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/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/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts new file mode 100644 index 000000000..d817775e5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda/test/test.cloudfront-apigateway-lambda.test.ts @@ -0,0 +1,179 @@ +/** + * 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 { CloudFrontToApiGatewayToLambda, CloudFrontToApiGatewayToLambdaProps } from "../lib"; +import * as cdk from "@aws-cdk/core"; +import * as lambda from '@aws-cdk/aws-lambda'; +import '@aws-cdk/assert/jest'; + +function deployNewFunc(stack: cdk.Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }; + + return new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + deployLambda: true, + lambdaFunctionProps + }); +} + +function useExistingFunc(stack: cdk.Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + return new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + deployLambda: false, + existingLambdaObj: new lambda.Function(stack, 'MyExistingFunction', lambdaFunctionProps) + }); +} + +test('snapshot test CloudFrontToApiGatewayToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: CloudFrontToApiGatewayToLambda = deployNewFunc(stack); + + expect(construct.cloudFrontWebDistribution()).toBeDefined(); + expect(construct.restApi()).toBeDefined(); + expect(construct.lambdaFunction()).toBeDefined(); +}); + +test('check lambda function properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs10.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + } + } + }); +}); + +test('check lambda function role for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "lambda.amazonaws.com" + } + } + ], + Version: "2012-10-17" + }, + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + Ref: "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + }); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: CloudFrontToApiGatewayToLambdaProps = { + deployLambda: false + }; + + try { + new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +test('check deploy = true and no prop', () => { + const stack = new cdk.Stack(); + + const props: CloudFrontToApiGatewayToLambdaProps = { + deployLambda: true + }; + + try { + new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +test('override api gateway properties', () => { + const stack = new cdk.Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }; + + const fn: lambda.Function = new lambda.Function(stack, 'MyExistingFunction', lambdaFunctionProps); + + new CloudFrontToApiGatewayToLambda(stack, 'test-cloudfront-apigateway-lambda', { + deployLambda: false, + existingLambdaObj: fn, + apiGatewayProps: { + handler: fn, + options: { + description: "Override description" + } + } + }); + + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', + { + Description: "Override description", + EndpointConfiguration: { + Types: [ + "REGIONAL" + ] + }, + Name: "RestApi" + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.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-cloudfront-apigateway/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.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-cloudfront-apigateway/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/.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-cloudfront-apigateway/README.md b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md new file mode 100644 index 000000000..1ad33c9c5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/README.md @@ -0,0 +1,82 @@ +# aws-cloudfront-apigateway 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/aws-cloudfront-apigateway/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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 stack = new Stack(); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +const func = defaults.deployLambdaFunction(stack, lambdaProps); + +const _api = defaults.RegionalApiGateway(stack, func); + +new CloudFrontToApiGateway(stack, 'test-cloudfront-apigateway', { + existingApiGatewayObj: _api +}); + +``` + +## Initializer + +``` text +new CloudFrontToApiGateway(scope: Construct, id: string, props: CloudFrontToApiGatewayProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`CloudFrontToApiGatewayProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Retruns 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.| + +## 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-konstruk/aws-cloudfront-apigateway/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..a5a667d5af73a8c5c6fbae3ce35e825fb89aab03 GIT binary patch literal 98447 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
fhRv%0wqDB)0TA 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�m8mw|pX3U>U)7G{+6Ud_`ZF^j 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 m;@Cd`^#4UIoO}SlNtY6%^` { + 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/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.ts new file mode 100644 index 000000000..4f73c8375 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-apigateway/test/test.cloudfront-apigateway.test.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 { 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 lambda from '@aws-cdk/aws-lambda'; +import '@aws-cdk/assert/jest'; + +function deploy(stack: cdk.Stack) { + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }; + + const func = defaults.deployLambdaFunction(stack, inProps); + + const _api = defaults.RegionalLambdaRestApi(stack, func); + + return new CloudFrontToApiGateway(stack, 'test-cloudfront-apigateway', { + existingApiGatewayObj: _api + }); +} + +test('snapshot test CloudFrontToApiGateway default params', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: CloudFrontToApiGateway = deploy(stack); + + expect(construct.cloudFrontWebDistribution()).toBeDefined(); + expect(construct.restApi()).toBeDefined(); +}); + +test('test cloudfront DomainName', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResourceLike("AWS::CloudFront::Distribution", { + DistributionConfig: { + Origins: [ + { + DomainName: { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "://", + { + "Fn::Join": [ + "", + [ + "https://", + { + Ref: "RestApi0C43BF4B" + }, + ".execute-api.", + { + Ref: "AWS::Region" + }, + ".", + { + Ref: "AWS::URLSuffix" + }, + "/", + { + Ref: "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + } + ] + } + } + ] + } + }, ResourcePart.Properties); +}); + +test('test api gateway lambda service role', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResource("AWS::IAM::Role", { + 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" + } + ] + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.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-cloudfront-s3/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.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-cloudfront-s3/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/.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-cloudfront-s3/README.md b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md new file mode 100644 index 000000000..81ef6f5a8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/README.md @@ -0,0 +1,71 @@ +# aws-cloudfront-s3 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/aws-cloudfront-s3/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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'); + +new CloudFrontToS3(stack, 'test-cloudfront-s3', { + deployBucket: true +}); + +``` + +## Initializer + +``` text +new CloudFrontToS3(scope: Construct, id: string, props: CloudFrontToS3Props); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`CloudFrontToS3Props`](#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| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|cloudFrontWebDistribution()|[`cloudfront.CloudFrontWebDistribution`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cloudfront.CloudFrontWebDistribution.html)|Retruns 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)|Retruns an instance of s3.Bucket created by the construct| + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-cloudfront-s3/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..a1721ffb2c8927ad2998631aa75a1ec7dfbc0547 GIT binary patch literal 102379 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 LRo|4q1_%8wC { + const stack = new cdk.Stack(); + deploy(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check s3Bucket default encryption', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [{ + ServerSideEncryptionByDefault : { + SSEAlgorithm: "AES256" + } + }] + } + }); +}); + +test('check s3Bucket public access block configuration', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResource('AWS::S3::Bucket', { + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true + } + }); +}); + +test('test s3Bucket override publicAccessBlockConfiguration', () => { + 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 + }, + }); +}); + +test('check existing bucket', () => { + const stack = new cdk.Stack(); + + const existingBucket = new s3.Bucket(stack, 'my-bucket', { + bucketName: 'my-bucket' + }); + + const props: CloudFrontToS3Props = { + deployBucket: false, + existingBucketObj: existingBucket + }; + + new CloudFrontToS3(stack, 'test-cloudfront-s3', props); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + BucketName: "my-bucket" + }); + +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: CloudFrontToS3Props = { + deployBucket: false + }; + + try { + new CloudFrontToS3(stack, 'test-cloudfront-s3', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: CloudFrontToS3 = deploy(stack); + + expect(construct.cloudFrontWebDistribution()).toBeDefined(); + expect(construct.bucket()).toBeDefined(); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-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-cognito-apigateway-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-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/aws-cognito-apigateway-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-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-cognito-apigateway-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md new file mode 100644 index 000000000..d74bb723a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/README.md @@ -0,0 +1,83 @@ +# aws-cognito-apigateway-lambda 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/aws-cognito-apigateway-lambda/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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 stack = new Stack(app, 'test-cognito-apigateway-lambda-stack'); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +new CognitoToApiGatewayToLambda(stack, 'test-cognito-apigateway-lambda', { + lambdaFunctionProps: lambdaProps, + deployLambda: true +}); +``` + +## Initializer + +``` text +new CognitoToApiGatewayToLambda(scope: Construct, id: string, props: CognitoToApiGatewayToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`CognitoToApiGatewayToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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.LambdaRestApi.html)|Optional user provided props to override the default props for API Gateway| +|cognitoUserPoolProps?|[`cognito.UserPoolProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolProps.html)|Optional user provided props to override the default props for Cognito User Pool| +|cognitoUserPoolClientProps?|[`cognito.UserPoolClientProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-cognito.UserPoolClientProps.html)|Optional user provided props to override the default props for Cognito User Pool Client| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|restApi()|[`api.RestApi`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.RestApi.html)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns an instance of cognito.UserPoolClient created by the construct| + +## 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-konstruk/aws-cognito-apigateway-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..69024a03fea7af4cc2aebe18f01d471664228c53 GIT binary patch literal 83951 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts new file mode 100644 index 000000000..4d0569169 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/lib/index.ts @@ -0,0 +1,150 @@ +/** + * 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 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 { Construct } from '@aws-cdk/core'; + +/** + * @summary The properties for the CognitoToApiGatewayToLambda Construct + */ +export interface CognitoToApiGatewayToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide a lambda function object as `existingLambdaObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided props to override the default props for the Lambda function. + * If `deploy` is set to true only then this property is required + * + * @default - Default props are used + */ + readonly lambdaFunctionProps?: lambda.FunctionProps + /** + * Optional user provided props to override the default props for the API Gateway. + * + * @default - Default props are used + */ + readonly apiGatewayProps?: api.LambdaRestApiProps | any + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly cognitoUserPoolProps?: cognito.UserPoolProps + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly cognitoUserPoolClientProps?: cognito.UserPoolClientProps +} + +export class CognitoToApiGatewayToLambda extends Construct { + private userpool: cognito.UserPool; + private userpoolclient: cognito.UserPoolClient; + private api: api.RestApi; + private fn: lambda.Function; + + /** + * @summary Constructs a new instance of the CognitoToApiGatewayToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CognitoToApiGatewayToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: CognitoToApiGatewayToLambdaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + this.api = defaults.GlobalLambdaRestApi(scope, this.fn, props.apiGatewayProps); + this.userpool = defaults.buildUserPool(scope, props.cognitoUserPoolProps); + this.userpoolclient = defaults.buildUserPoolClient(scope, this.userpool, props.cognitoUserPoolClientProps); + + const cfnAuthorizer = new api.CfnAuthorizer(scope, 'CognitoAuthorizer', { + restApiId: this.api.restApiId, + type: 'COGNITO_USER_POOLS', + providerArns: [this.userpool.userPoolArn], + identitySource: "method.request.header.Authorization", + name: "authorizer" + }); + + this.api.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') { + child.addPropertyOverride('AuthorizationType', 'NONE'); + } else { + child.addPropertyOverride('AuthorizationType', 'COGNITO_USER_POOLS'); + child.addPropertyOverride('AuthorizerId', { Ref: cfnAuthorizer.logicalId }); + } + }); + } + + /** + * @summary Retruns 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 Retruns 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 Retruns 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 Retruns 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-konstruk/aws-cognito-apigateway-lambda/package.json new file mode 100644 index 000000000..fae825011 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/package.json @@ -0,0 +1,79 @@ +{ + "name": "@aws-solutions-konstruk/aws-cognito-apigateway-lambda", + "version": "0.8.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" + }, + "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.cognitoapigatewaylambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "cognitoapigatewaylambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.CognitoApigatewayLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} 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-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap new file mode 100644 index 000000000..8ca2165cf --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/__snapshots__/test.cognito-apigateway-lambda.test.js.snap @@ -0,0 +1,620 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test CognitoToApiGatewayToLambda default params 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "CognitoAuthorizer": Object { + "Properties": Object { + "IdentitySource": "method.request.header.Authorization", + "Name": "authorizer", + "ProviderARNs": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "Arn", + ], + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Type": "COGNITO_USER_POOLS", + }, + "Type": "AWS::ApiGateway::Authorizer", + }, + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": Object { + "Ref": "CognitoAuthorizer", + }, + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionRestApiANY3A99B4EE": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestRestApiANY79BD91F2": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "ApiAccessLogGroupCEA70788", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": Object { + "Ref": "CognitoAuthorizer", + }, + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionRestApiANYproxy9C9912F9": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestRestApiANYproxyCB7BC56D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..a66b08eae --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.expected.json @@ -0,0 +1,616 @@ +{ + "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." + } + ] + } + } + }, + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "ApiAccessLogGroupCEA70788", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "RestApiproxyC95856DD": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "RestApi0C43BF4B" + } + } + }, + "RestApiproxyANYApiPermissiontestcognitoapigatewaylambdastackRestApi7173ADDDANYproxyEE672F74": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANYApiPermissionTesttestcognitoapigatewaylambdastackRestApi7173ADDDANYproxyF4659A23": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANY1786B242": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "RestApiproxyC95856DD" + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "CognitoAuthorizer" + }, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiANYApiPermissiontestcognitoapigatewaylambdastackRestApi7173ADDDANY5461DBBA": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/" + ] + ] + } + } + }, + "RestApiANYApiPermissionTesttestcognitoapigatewaylambdastackRestApi7173ADDDANYB2D94B09": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "RestApiANYA7C1DC94": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "CognitoAuthorizer" + }, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiUsagePlan6E1C537A": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Stage": { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "Throttle": {} + } + ] + } + }, + "ApiAccessLogGroupCEA70788": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LambdaRestApiCloudWatchRoleF339D4E6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "LambdaRestApiAccount": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + }, + "CognitoUserPool53E37E69": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "LambdaConfig": {}, + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + } + } + }, + "CognitoUserPoolClient5AB59AE4": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + } + }, + "CognitoAuthorizer": { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Type": "COGNITO_USER_POOLS", + "IdentitySource": "method.request.header.Authorization", + "Name": "authorizer", + "ProviderARNs": [ + { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "Arn" + ] + } + ] + } + } + }, + "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\"" + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts new file mode 100644 index 000000000..dc9513a90 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/integ.no-arguments.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { CognitoToApiGatewayToLambda } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-cognito-apigateway-lambda-stack'); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +new CognitoToApiGatewayToLambda(stack, 'test-cognito-apigateway-lambda', { + lambdaFunctionProps: lambdaProps, + deployLambda: true +}); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/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/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts new file mode 100644 index 000000000..6beea624a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-cognito-apigateway-lambda/test/test.cognito-apigateway-lambda.test.ts @@ -0,0 +1,96 @@ +/** + * 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 { CognitoToApiGatewayToLambda, CognitoToApiGatewayToLambdaProps } from "../lib"; +import * as cdk from "@aws-cdk/core"; +import * as cognito from '@aws-cdk/aws-cognito'; +import * as lambda from '@aws-cdk/aws-lambda'; +import '@aws-cdk/assert/jest'; + +function deployNewFunc(stack: cdk.Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }; + + return new CognitoToApiGatewayToLambda(stack, 'test-cognito-apigateway-lambda', { + deployLambda: true, + lambdaFunctionProps + }); +} + +test('snapshot test CognitoToApiGatewayToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('override cognito properties', () => { + const stack = new cdk.Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }; + + const cognitoUserPoolProps: cognito.UserPoolProps = { + userPoolName: 'test', + autoVerifiedAttributes: [cognito.UserPoolAttribute.EMAIL] + }; + + new CognitoToApiGatewayToLambda(stack, 'test-cognito-apigateway-lambda', { + deployLambda: true, + lambdaFunctionProps, + cognitoUserPoolProps + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPool', + { + AutoVerifiedAttributes: [ + "email" + ], + LambdaConfig: {}, + UserPoolAddOns: { + AdvancedSecurityMode: "ENFORCED" + }, + UserPoolName: "test" + }); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: CognitoToApiGatewayToLambdaProps = { + deployLambda: false + }; + + try { + new CognitoToApiGatewayToLambda(stack, 'test-cognito-apigateway-lambda', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +test('check getter methods', () => { + 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(); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.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-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.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-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/.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-dynamodb-stream-lambda-elasticsearch-kibana/README.md b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md new file mode 100644 index 000000000..4168da891 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/README.md @@ -0,0 +1,86 @@ +# aws-dynamodb-stream-lambda-elasticsearch-kibana 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/aws-dynamodb-stream-lambda-elasticsearch-kibana/| +|:-------------|:-------------| +

+ +| **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`| + +This AWS Solutions Konstruk 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 props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + domainName: 'test-domain' +}; + +new DynamoDBStreamToLambdaToElasticSearchAndKibana(stack, 'test-dynamodb-stream-lambda-elasticsearch-kibana', props); +``` + +## Initializer + +``` text +new DynamoDBStreamToLambdaToElasticSearchAndKibana(scope: Construct, id: string, props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`DynamoDBStreamToLambdaToElasticSearchAndKibanaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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| +|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| +|dynamoEventSourceProps?|[`aws-lambda-event-sources.DynamoEventSourceProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda-event-sources.DynamoEventSourceProps.html)|Optional user provided props to override the default props for DynamoDB Event Source| +|esDomainProps?|[`elasticsearch.CfnDomainProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomainProps.html)|Optional user provided props to override the default props for the Elasticsearch Service| +|domainName|`string`|Domain name for the Cognito and the Elasticsearch Service| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns a list of cloudwatch.Alarm created by the construct| + +## 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-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..84193e88a1a7f3dd132cc4dd3811bee12064cc1b GIT binary patch literal 127731 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts new file mode 100644 index 000000000..513a57dd4 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/lib/index.ts @@ -0,0 +1,182 @@ +/** + * 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 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 * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as cognito from '@aws-cdk/aws-cognito'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; +import { Construct } from '@aws-cdk/core'; + +/** + * @summary The properties for the DynamoDBStreamToLambdaToElastciSearchAndKibana Construct + */ +export interface DynamoDBStreamToLambdaToElasticSearchAndKibanaProps { + /** + * Whether to create a new lambda function or use an existing lambda function. + * If set to false, you must provide a lambda function object as `existingObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * 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 lambdaFunctionProps?: lambda.FunctionProps, + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly dynamoTableProps?: dynamodb.TableProps, + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly dynamoEventSourceProps?: DynamoEventSourceProps, + /** + * Optional user provided props to override the default props for the API Gateway. + * + * @default - Default props are used + */ + readonly esDomainProps?: elasticsearch.CfnDomainProps, + /** + * Cognito & ES Domain Name + * + * @default - None + */ + readonly domainName: string +} + +export class DynamoDBStreamToLambdaToElasticSearchAndKibana extends Construct { + private dynamoDBStreamToLambda: DynamoDBStreamToLambda; + private lambdaToElasticSearchAndKibana: LambdaToElasticSearchAndKibana; + private fn: lambda.Function; + + /** + * @summary Constructs a new instance of the LambdaToDynamoDB class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {DynamoDBStreamToLambdaToElasticSearchAndKibanaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps) { + super(scope, id); + + const _props1: DynamoDBStreamToLambdaProps = { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps, + dynamoEventSourceProps: props.dynamoEventSourceProps, + dynamoTableProps: props.dynamoTableProps + }; + + this.dynamoDBStreamToLambda = new DynamoDBStreamToLambda(scope, 'DynamoDBStreamToLambda', _props1); + + this.fn = this.dynamoDBStreamToLambda.lambdaFunction(); + + const _props2: LambdaToElasticSearchAndKibanaProps = { + deployLambda: false, + existingLambdaObj: this.fn, + domainName: props.domainName, + esDomainProps: props.esDomainProps + }; + + this.lambdaToElasticSearchAndKibana = new LambdaToElasticSearchAndKibana(scope, 'LambdaToElasticSearch', _props2); + } + + /** + * @summary Retruns 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 Retruns 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 Retruns 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 Retruns 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 Retruns 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 Retruns 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 Retruns 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(); + } +} 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 new file mode 100644 index 000000000..acdb920fd --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/package.json @@ -0,0 +1,89 @@ +{ + "name": "@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana", + "version": "0.8.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-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.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-solutions-konstruk/aws-dynamodb-stream-lambda": "~0.8.0", + "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-solutions-konstruk/aws-dynamodb-stream-lambda": "~0.8.0", + "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana": "~0.8.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0" + } +} 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-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap new file mode 100644 index 000000000..4d657e049 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/__snapshots__/dynamodb-stream-lambda-elasticsearch-kibana.test.js.snap @@ -0,0 +1,681 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197ArtifactHash052E3F31": Object { + "Description": "Artifact hash for asset \\"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\\"", + "Type": "String", + }, + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3Bucket87AE2D86": Object { + "Description": "S3 bucket for asset \\"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\\"", + "Type": "String", + }, + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907": Object { + "Description": "S3 key for asset version \\"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\\"", + "Type": "String", + }, + }, + "Resources": Object { + "AutomatedSnapshotFailureTooHighAlarmA7918D4F": Object { + "Properties": Object { + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "CPUUtilizationTooHighAlarmA395C469": Object { + "Properties": Object { + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "CognitoAuthorizedRole14E74FE0": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": Object { + "ForAnyValue:StringLike": Object { + "cognito-identity.amazonaws.com:amr": "authenticated", + }, + "StringEquals": Object { + "cognito-identity.amazonaws.com:aud": Object { + "Ref": "CognitoIdentityPool", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoAccessPolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CognitoIdentityPool": Object { + "Properties": Object { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": Array [ + Object { + "ClientId": Object { + "Ref": "CognitoUserPoolClient5AB59AE4", + }, + "ProviderName": Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "ProviderName", + ], + }, + "ServerSideTokenCheck": true, + }, + ], + }, + "Type": "AWS::Cognito::IdentityPool", + }, + "CognitoKibanaConfigureRole62CCE76A": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "es.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "CognitoKibanaConfigureRolePolicy76F46A5E": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:cognito-identity:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":identitypool/", + Object { + "Ref": "CognitoIdentityPool", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain", + ], + ], + }, + ], + }, + Object { + "Action": "iam:PassRole", + "Condition": Object { + "StringLike": Object { + "iam:PassedToService": "cognito-identity.amazonaws.com", + }, + }, + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoKibanaConfigureRolePolicy76F46A5E", + "Roles": Array [ + Object { + "Ref": "CognitoKibanaConfigureRole62CCE76A", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + "DynamoDBStreamToLambdaDynamoTable900492E8": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "id", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "id", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + "StreamSpecification": Object { + "StreamViewType": "NEW_AND_OLD_IMAGES", + }, + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + "ElasticsearchDomain": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only", + }, + ], + }, + }, + "Properties": Object { + "AccessPolicies": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": Object { + "AWS": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + ], + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "CognitoOptions": Object { + "Enabled": true, + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "DomainName": "test-domain", + "EBSOptions": Object { + "EBSEnabled": true, + "VolumeSize": 10, + }, + "ElasticsearchClusterConfig": Object { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": Object { + "AvailabilityZoneCount": 3, + }, + "ZoneAwarenessEnabled": true, + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": Object { + "Enabled": true, + }, + "NodeToNodeEncryptionOptions": Object { + "Enabled": true, + }, + "SnapshotOptions": Object { + "AutomatedSnapshotStartHour": 1, + }, + }, + "Type": "AWS::Elasticsearch::Domain", + }, + "FreeStorageSpaceTooLowAlarm3410CBE2": Object { + "Properties": Object { + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 2000, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "IdentityPoolRoleMapping": Object { + "Properties": Object { + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "Roles": Object { + "authenticated": Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + }, + "IndexWritesBlockedTooHighAlarm5F7E9A55": Object { + "Properties": Object { + "AlarmDescription": "Your cluster is blocking write requests.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "JVMMemoryPressureTooHighAlarm303EEA7C": Object { + "Properties": Object { + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3Bucket87AE2D86", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DOMAIN_ENDPOINT": Object { + "Fn::GetAtt": Array [ + "ElasticsearchDomain", + "DomainEndpoint", + ], + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionDynamoDBEventSourceDynamoDBStreamToLambdaDynamoTableDA56B777DD1406A9": Object { + "Properties": Object { + "BatchSize": 100, + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "StreamArn", + ], + }, + "FunctionName": Object { + "Ref": "LambdaFunctionBF21E41F", + }, + "StartingPosition": "TRIM_HORIZON", + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "Arn", + ], + }, + "/stream/*", + ], + ], + }, + }, + Object { + "Action": Array [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "StreamArn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "MasterCPUUtilizationTooHighAlarm1CE1084B": Object { + "Properties": Object { + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "MasterJVMMemoryPressureTooHighAlarmBB15F770": Object { + "Properties": Object { + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "StatusRedAlarm4CE918C2": Object { + "Properties": Object { + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "StatusYellowAlarm2B20F083": Object { + "Properties": Object { + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "UserPoolDomain": Object { + "DependsOn": Array [ + "CognitoUserPool53E37E69", + ], + "Properties": Object { + "Domain": "test-domain", + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolDomain", + }, + }, +} +`; 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-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts new file mode 100644 index 000000000..f5bb58637 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/dynamodb-stream-lambda-elasticsearch-kibana.test.ts @@ -0,0 +1,88 @@ +/** + * 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 { 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) { + const props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + domainName: 'test-domain' + }; + + return new DynamoDBStreamToLambdaToElasticSearchAndKibana(stack, 'test-dynamodb-stream-lambda-elasticsearch-stack', props); +} + +test('snapshot test default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check domain names', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolDomain', { + Domain: "test-domain", + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + } + }); + + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + }); +}); + +test('check getter methods', () => { + 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); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { + deployLambda: true, + domainName: 'test-domain' + }; + + try { + new DynamoDBStreamToLambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-stack', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file 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-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..fb78c8e33 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json @@ -0,0 +1,677 @@ +{ + "Resources": { + "DynamoDBStreamToLambdaDynamoTable900492E8": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "Arn" + ] + }, + "/stream/*" + ] + ] + } + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "StreamArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3Bucket87AE2D86" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "ElasticsearchDomain", + "DomainEndpoint" + ] + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "LambdaFunctionDynamoDBEventSourcetestdynamodbstreamlambdaelasticsearchkibanastackDynamoDBStreamToLambdaDynamoTableF3692FDE826139F4": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "DynamoDBStreamToLambdaDynamoTable900492E8", + "StreamArn" + ] + }, + "FunctionName": { + "Ref": "LambdaFunctionBF21E41F" + }, + "BatchSize": 100, + "StartingPosition": "TRIM_HORIZON" + } + }, + "CognitoUserPool53E37E69": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "LambdaConfig": {}, + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + } + } + }, + "CognitoUserPoolClient5AB59AE4": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + } + }, + "CognitoIdentityPool": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "CognitoUserPoolClient5AB59AE4" + }, + "ProviderName": { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "UserPoolDomain": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "myvesperdomain", + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + }, + "DependsOn": [ + "CognitoUserPool53E37E69" + ] + }, + "CognitoAuthorizedRole14E74FE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "CognitoIdentityPool" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/myvesperdomain/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "IdentityPoolRoleMapping": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "CognitoIdentityPool" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + } + } + } + }, + "CognitoKibanaConfigureRole62CCE76A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "CognitoKibanaConfigureRolePolicy76F46A5E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "CognitoIdentityPool" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/myvesperdomain" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoKibanaConfigureRolePolicy76F46A5E", + "Roles": [ + { + "Ref": "CognitoKibanaConfigureRole62CCE76A" + } + ] + } + }, + "ElasticsearchDomain": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/myvesperdomain/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "CognitoIdentityPool" + }, + "RoleArn": { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + }, + "DomainName": "myvesperdomain", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 3 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + } + ] + } + } + }, + "StatusRedAlarm4CE918C2": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "StatusYellowAlarm2B20F083": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "FreeStorageSpaceTooLowAlarm3410CBE2": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 2000 + } + }, + "IndexWritesBlockedTooHighAlarm5F7E9A55": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "AutomatedSnapshotFailureTooHighAlarmA7918D4F": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "CPUUtilizationTooHighAlarmA395C469": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "JVMMemoryPressureTooHighAlarm303EEA7C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "MasterCPUUtilizationTooHighAlarm1CE1084B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "MasterJVMMemoryPressureTooHighAlarmBB15F770": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + } + }, + "Parameters": { + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3Bucket87AE2D86": { + "Type": "String", + "Description": "S3 bucket for asset \"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\"" + }, + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197S3VersionKey6EF53907": { + "Type": "String", + "Description": "S3 key for asset version \"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\"" + }, + "AssetParameters92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197ArtifactHash052E3F31": { + "Type": "String", + "Description": "Artifact hash for asset \"92927de5fcc3aea277bddecb845bee318fb502f7375daedbdafb72c0400bc197\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts new file mode 100644 index 000000000..d34bf0dce --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/integ.no-arguments.ts @@ -0,0 +1,34 @@ +/** + * 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 { DynamoDBStreamToLambdaToElasticSearchAndKibanaProps, DynamoDBStreamToLambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Empty arguments +const stack = new Stack(app, 'test-dynamodb-stream-lambda-elasticsearch-kibana-stack'); + +const props: DynamoDBStreamToLambdaToElasticSearchAndKibanaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + domainName: 'myvesperdomain' +}; + +new DynamoDBStreamToLambdaToElasticSearchAndKibana(stack, 'test-dynamodb-stream-lambda-elasticsearch-kibana', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js new file mode 100644 index 000000000..6d89b241c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda-elasticsearch-kibana/test/lambda/index.js @@ -0,0 +1,68 @@ +var AWS = require('aws-sdk'); +var path = require('path'); + +console.log('Loading function'); + +var esDomain = { + endpoint: process.env.DOMAIN_ENDPOINT, + region: process.env.AWS_REGION, + index: 'lambda-index', + doctype: 'lambda-type' +}; + +var creds = new AWS.EnvironmentCredentials('AWS'); +var endpoint = new AWS.Endpoint(esDomain.endpoint); + +function postDocumentToES(doc, context, id) { + var req = new AWS.HttpRequest(endpoint); + + req.method = 'POST'; + req.path = path.join('/', esDomain.index, esDomain.doctype, id); + req.region = esDomain.region; + req.body = doc; + req.headers['presigned-expires'] = false; + req.headers['Host'] = esDomain.endpoint; + req.headers['Content-Type'] = 'application/json'; + + // Sign the request (Sigv4) + var signer = new AWS.Signers.V4(req, 'es'); + signer.addAuthorization(creds, new Date()); + + // Post document to ES + var send = new AWS.NodeHttpClient(); + send.handleRequest(req, null, function(httpResp) { + var body = ''; + httpResp.on('data', function (chunk) { + body += chunk; + }); + httpResp.on('end', function (chunk) { + console.log('DynamoDB record added to ES.'); + context.succeed(); + }); + }, function(err) { + console.log('Error: ' + err); + context.fail(); + }); +} + +exports.handler = (event, context) => { + console.log('Received event:', JSON.stringify(event, null, 2)); + let count = 0; + + event.Records.forEach((record) => { + const id = record.dynamodb.Keys.id.S; + + if (record.dynamodb.NewImage) { + postDocumentToES(JSON.stringify(record.dynamodb.NewImage), context, id); + } + + count += 1 + }); + + + return { +      statusCode: 200, +      headers: { 'Content-Type': 'text/plain' }, +      body: `${count} records processed.\n` +    }; +}; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-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-dynamodb-stream-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-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/aws-dynamodb-stream-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-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-dynamodb-stream-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md new file mode 100644 index 000000000..551786f07 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/README.md @@ -0,0 +1,79 @@ +# aws-dynamodb-stream-lambda 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/aws-dynamodb-stream-lambda/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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 props: DynamoDBStreamToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, +}; + +new DynamoDBStreamToLambda(stack, 'test-dynamodb-stream-lambda', props); + +``` + +## Initializer + +``` text +new DynamoDBStreamToLambda(scope: Construct, id: string, props: DynamoDBStreamToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`DynamoDBStreamToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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| +|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| +|dynamoEventSourceProps?|[`aws-lambda-event-sources.DynamoEventSourceProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda-event-sources.DynamoEventSourceProps.html)|Optional user provided props to override the default props for DynamoDB Event Source| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|dynamoTable()|[`dynamodb.Table`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html)|Retruns 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)|Retruns an instance of lambda.Function created by the construct| + +## 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-konstruk/aws-dynamodb-stream-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5382db3a00e5717897216e0a7de97d79d0afd2 GIT binary patch literal 71390 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 Ra1HlUf1;&aqG%TKzW@_|CSCvl literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts new file mode 100644 index 000000000..b5ed15ea8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/lib/index.ts @@ -0,0 +1,117 @@ +/** + * 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 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 { Construct } from '@aws-cdk/core'; +import { overrideProps } from '@aws-solutions-konstruk/core'; + +/** + * @summary The properties for the DynamoDBStreamToLambda Construct + */ +export interface DynamoDBStreamToLambdaProps { + /** + * Whether to create a new lambda function or use an existing lambda function. + * If set to false, you must provide a lambda function object as `existingObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * 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 lambdaFunctionProps?: lambda.FunctionProps, + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly dynamoTableProps?: dynamodb.TableProps, + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly dynamoEventSourceProps?: DynamoEventSourceProps +} + +export class DynamoDBStreamToLambda extends Construct { + private fn: lambda.Function; + private table: dynamodb.Table; + + /** + * @summary Constructs a new instance of the LambdaToDynamoDB class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {DynamoDBStreamToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: DynamoDBStreamToLambdaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // 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); + } else { + this.table = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableWithStreamProps); + } + + // Grant DynamoDB Stream read perimssion for lambda function + this.table.grantStreamRead(this.fn.grantPrincipal); + + // Create DynamDB trigger to invoke lambda function + this.fn.addEventSource(new DynamoEventSource(this.table, + defaults.DynamoEventSourceProps(props.dynamoEventSourceProps))); + } + + /** + * @summary Retruns 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 Retruns 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-konstruk/aws-dynamodb-stream-lambda/package.json new file mode 100644 index 000000000..5490e69aa --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/package.json @@ -0,0 +1,79 @@ +{ + "name": "@aws-solutions-konstruk/aws-dynamodb-stream-lambda", + "version": "0.8.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" + }, + "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.dynamodbstreamlambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "dynamodbstreamlambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.DynamodbStreamLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0" + } +} 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-konstruk/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap new file mode 100644 index 000000000..5a97cea2a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/__snapshots__/dynamodb-stream-lambda.test.js.snap @@ -0,0 +1,233 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test DynamoDBStreamToLambda default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": Object { + "Description": "Artifact hash for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": Object { + "Description": "S3 bucket for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": Object { + "Description": "S3 key for asset version \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionDynamoDBEventSourcetestlambdadynamodbstackDynamoTableD6E2BCEF52F21754": Object { + "Properties": Object { + "BatchSize": 100, + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "testlambdadynamodbstackDynamoTable8138E93B", + "StreamArn", + ], + }, + "FunctionName": Object { + "Ref": "LambdaFunctionBF21E41F", + }, + "StartingPosition": "TRIM_HORIZON", + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn", + ], + }, + "/stream/*", + ], + ], + }, + }, + Object { + "Action": Array [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "testlambdadynamodbstackDynamoTable8138E93B", + "StreamArn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testlambdadynamodbstackDynamoTable8138E93B": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "id", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "id", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + "StreamSpecification": Object { + "StreamViewType": "NEW_AND_OLD_IMAGES", + }, + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts new file mode 100644 index 000000000..d56861454 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/dynamodb-stream-lambda.test.ts @@ -0,0 +1,206 @@ +/** + * 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 { DynamoDBStreamToLambda, DynamoDBStreamToLambdaProps } 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 '@aws-cdk/assert/jest'; + +function deployNewFunc(stack: cdk.Stack) { + const props: DynamoDBStreamToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + }; + + return new DynamoDBStreamToLambda(stack, 'test-lambda-dynamodb-stack', props); +} + +test('snapshot test DynamoDBStreamToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check lambda EventSourceMapping', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::EventSourceMapping', { + EventSourceArn: { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "StreamArn" + ] + }, + FunctionName: { + Ref: "LambdaFunctionBF21E41F" + }, + BatchSize: 100, + StartingPosition: "TRIM_HORIZON" + }); +}); + +test('check DynamoEventSourceProps override', () => { + const stack = new cdk.Stack(); + const props: DynamoDBStreamToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + dynamoEventSourceProps: { + startingPosition: lambda.StartingPosition.LATEST, + batchSize: 55 + } + }; + + new DynamoDBStreamToLambda(stack, 'test-lambda-dynamodb-stack', props); + + expect(stack).toHaveResource('AWS::Lambda::EventSourceMapping', { + EventSourceArn: { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "StreamArn" + ] + }, + FunctionName: { + Ref: "LambdaFunctionBF21E41F" + }, + BatchSize: 55, + StartingPosition: "LATEST" + }); +}); + +test('check lambda permission to read dynamodb stream', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "dynamodb:ListStreams", + Effect: "Allow", + Resource: { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + "/stream/*" + ] + ] + } + }, + { + Action: [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + Effect: "Allow", + Resource: { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "StreamArn" + ] + } + } + ], + Version: "2012-10-17" + }, + PolicyName: "LambdaFunctionServiceRoleDefaultPolicy126C8897", + Roles: [ + { + Ref: "LambdaFunctionServiceRole0C4CDE0B" + } + ] + }); +}); + +test('check dynamodb table stream override', () => { + const stack = new cdk.Stack(); + const props: DynamoDBStreamToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + dynamoTableProps: { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + }, + stream: dynamodb.StreamViewType.NEW_IMAGE + } + }; + + new DynamoDBStreamToLambda(stack, 'test-lambda-dynamodb-stack', props); + expect(stack).toHaveResource('AWS::DynamoDB::Table', { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + } + ], + BillingMode: "PAY_PER_REQUEST", + SSESpecification: { + SSEEnabled: true + }, + StreamSpecification: { + StreamViewType: "NEW_IMAGE" + } + }); + +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: DynamoDBStreamToLambda = deployNewFunc(stack); + + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.dynamoTable()).toBeInstanceOf(dynamodb.Table); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: DynamoDBStreamToLambdaProps = { + deployLambda: true + }; + + try { + new DynamoDBStreamToLambda(stack, 'test-iot-lambda-integration', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..f8b01d1c1 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.expected.json @@ -0,0 +1,229 @@ +{ + "Resources": { + "testdynamodbstreamlambdaDynamoTable6EB2ED0F": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "dynamodb:ListStreams", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testdynamodbstreamlambdaDynamoTable6EB2ED0F", + "Arn" + ] + }, + "/stream/*" + ] + ] + } + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testdynamodbstreamlambdaDynamoTable6EB2ED0F", + "StreamArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "LambdaFunctionDynamoDBEventSourcetestdynamodbstreamlambdastacktestdynamodbstreamlambdaDynamoTable99DF98248D8C4DDC": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "testdynamodbstreamlambdaDynamoTable6EB2ED0F", + "StreamArn" + ] + }, + "FunctionName": { + "Ref": "LambdaFunctionBF21E41F" + }, + "BatchSize": 100, + "StartingPosition": "TRIM_HORIZON" + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts new file mode 100644 index 000000000..d50708b3c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/integ.no-arguments.ts @@ -0,0 +1,33 @@ +/** + * 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 { DynamoDBStreamToLambdaProps, DynamoDBStreamToLambda } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Empty arguments +const stack = new Stack(app, 'test-dynamodb-stream-lambda-stack'); + +const props: DynamoDBStreamToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, +}; + +new DynamoDBStreamToLambda(stack, 'test-dynamodb-stream-lambda', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js new file mode 100644 index 000000000..743e4fdbb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-dynamodb-stream-lambda/test/lambda/index.js @@ -0,0 +1,8 @@ +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `Hello, CDK! You've hit ${event.path}\n` + }; + }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-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-events-rule-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-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/aws-events-rule-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-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-events-rule-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md new file mode 100644 index 000000000..bab9cdad3 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/README.md @@ -0,0 +1,80 @@ +# aws-events-rule-lambda 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/aws-events-rule-lambda/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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 props: EventsRuleToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +}; + +new EventsRuleToLambda(stack, 'test-events-rule-lambda', props); +``` + +## Initializer + +``` text +new EventsRuleToLambda(scope: Construct, id: string, props: EventsRuleToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`EventsRuleToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new lambda function or use an existing lambda function| +|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| +|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)|Retruns 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)|Retruns an instance of lambda.Function created by the construct| + +## 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-konstruk/aws-events-rule-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..cab5375d6d8831375e9314899d11d35b51cf5ee2 GIT binary patch literal 53264 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�mQq4cONjM&bVzPnEn5uX>W@IIU3v1fW& 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts new file mode 100644 index 000000000..037d09f4e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/lib/index.ts @@ -0,0 +1,112 @@ +/** + * 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 lambda from '@aws-cdk/aws-lambda'; +import * as events from '@aws-cdk/aws-events'; +import * as defaults from '@aws-solutions-konstruk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import { Construct } from '@aws-cdk/core'; +import { overrideProps } from '@aws-solutions-konstruk/core'; + +/** + * @summary The properties for the CloudFrontToApiGateway Construct + */ +export interface EventsRuleToLambdaProps { + /** + * Whether to create a new lambda function or use an existing lambda function. + * If set to false, you must provide a lambda function object as `existingObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * 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 lambdaFunctionProps?: lambda.FunctionProps, + /** + * User provided eventRuleProps to override the defaults + * + * @default - None + */ + readonly eventRuleProps: events.RuleProps +} + +export class EventsRuleToLambda extends Construct { + private fn: lambda.Function; + private rule: events.Rule; + + /** + * @summary Constructs a new instance of the EventsRuleToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {EventsRuleToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: EventsRuleToLambdaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + const lambdaFunc: events.IRuleTarget = { + bind: () => ({ + id: '', + arn: this.fn.functionArn + }) + }; + + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([lambdaFunc]); + const eventsRuleProps = overrideProps(defaultEventsRuleProps, props.eventRuleProps, true); + + this.rule = new events.Rule(this, 'EventsRule', eventsRuleProps); + + this.fn.addPermission("LambdaInvokePermission", { + principal: new iam.ServicePrincipal('events.amazonaws.com'), + sourceArn: this.rule.ruleArn + }); + } + + /** + * @summary Retruns 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 Retruns 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-konstruk/aws-events-rule-lambda/package.json new file mode 100644 index 000000000..80bdf4d2c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/package.json @@ -0,0 +1,79 @@ +{ + "name": "@aws-solutions-konstruk/aws-events-rule-lambda", + "version": "0.8.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" + }, + "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.eventsrulelambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "eventsrulelambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.EventsRuleLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-events": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-events": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} 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-konstruk/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap new file mode 100644 index 000000000..b1fe7c586 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/__snapshots__/events-rule-lambda.test.js.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test EventsRuleToLambda default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionLambdaInvokePermissionC135C9F1": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "events.amazonaws.com", + "SourceArn": Object { + "Fn::GetAtt": Array [ + "testeventsrulelambdaEventsRule82B36872", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testeventsrulelambdaEventsRule82B36872": Object { + "Properties": Object { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Id": "Target0", + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts new file mode 100644 index 000000000..fcae801a4 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/events-rule-lambda.test.ts @@ -0,0 +1,189 @@ +/** + * 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 lambda from '@aws-cdk/aws-lambda'; +import * as events from '@aws-cdk/aws-events'; +import { EventsRuleToLambdaProps, EventsRuleToLambda } from '../lib/index'; +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; + +function deployNewFunc(stack: cdk.Stack) { + const props: EventsRuleToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + eventRuleProps: { + schedule: events.Schedule.rate(cdk.Duration.minutes(5)) + } + }; + + return new EventsRuleToLambda(stack, 'test-events-rule-lambda', props); +} + +test('snapshot test EventsRuleToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check lambda function properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs12.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" + } + } + }); +}); + +test('check lambda function permission for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + Principal: "events.amazonaws.com", + SourceArn: { + "Fn::GetAtt": [ + "testeventsrulelambdaEventsRule82B36872", + "Arn" + ] + } + }); +}); + +test('check lambda function role for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + 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" + } + ] + }); +}); + +test('check events rule properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Events::Rule', { + ScheduleExpression: "rate(5 minutes)", + State: "ENABLED", + Targets: [ + { + Arn: { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + Id: "Target0" + } + ] + }); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: EventsRuleToLambda = deployNewFunc(stack); + + expect(construct.eventsRule()).toBeInstanceOf(events.Rule); + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: EventsRuleToLambdaProps = { + deployLambda: false, + eventRuleProps: { + schedule: events.Schedule.rate(cdk.Duration.minutes(5)) + } + }; + + try { + new EventsRuleToLambda(stack, 'test-events-rule-lambda', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); 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-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json new file mode 100644 index 000000000..f22f4f2ee --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.expected.json @@ -0,0 +1,175 @@ +{ + "Resources": { + "testeventsrulelambdaEventsRule82B36872": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Id": "Target0" + } + ] + } + }, + "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." + } + ] + } + } + }, + "LambdaFunctionLambdaInvokePermissionC135C9F1": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "testeventsrulelambdaEventsRule82B36872", + "Arn" + ] + } + } + } + }, + "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-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts new file mode 100644 index 000000000..4bd3c48d5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/integ.events-rule-no-argument.ts @@ -0,0 +1,37 @@ +/** + * 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 { EventsRuleToLambda, EventsRuleToLambdaProps } from "../lib"; +import { Duration } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as events from '@aws-cdk/aws-events'; + +const app = new App(); +const stack = new Stack(app, 'test-events-rule-lambda-stack'); + +const props: EventsRuleToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + eventRuleProps: { + schedule: events.Schedule.rate(Duration.minutes(5)) + } +}; + +new EventsRuleToLambda(stack, 'test-events-rule-lambda', props); +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-events-rule-lambda/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/aws-iot-kinesisfirehose-s3/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.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-iot-kinesisfirehose-s3/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.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-iot-kinesisfirehose-s3/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/.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-iot-kinesisfirehose-s3/README.md b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md new file mode 100644 index 000000000..2062dca0e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/README.md @@ -0,0 +1,82 @@ +# aws-iot-kinesisfirehose-s3 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/aws-iot-kinesisfirehose-s3/| +|:-------------|:-------------| +
+ +| **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`| + +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. + +Here is a minimal deployable pattern definition: + +``` javascript +const { IotToKinesisFirehoseToS3Props, IotToKinesisFirehoseToS3 } = require('@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3'); + +const props: IotToKinesisFirehoseToS3Props = { + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Persistent storage of connected vehicle telematics data", + sql: "SELECT * FROM 'connectedcar/telemetry/#'", + actions: [] + } + } +}; + +new IotToKinesisFirehoseToS3(stack, 'test-iot-firehose-s3', props); + +``` + +## Initializer + +``` text +new IotToKinesisFirehoseToS3(scope: Construct, id: string, props: IotToKinesisFirehoseToS3Props); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`IotToKinesisFirehoseToS3Props`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|iotTopicRuleProps|[`iot.CfnTopicRuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRuleProps.html)|User provided CfnTopicRuleProps to override the defaults| +|kinesisFirehoseProps?|[`kinesisfirehose.CfnDeliveryStreamProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStreamProps.html)|Optional user provided props to override the default props for Kinesis Firehose Delivery Stream| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|kinesisFirehose()|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Retruns 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)|Retruns 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)|Retruns an instance of iot.CfnTopicRule created by the construct| + +## 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-konstruk/aws-iot-kinesisfirehose-s3/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-iot-kinesisfirehose-s3/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..5197c5860662822acd12dea8acfaf2374a2c9fd7 GIT binary patch literal 131500 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
(K`CV{(FhpC+WFVA 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%0DpTHsJ}c2w78+m5d6vf$CI zo%gg*yF;1R@dDSW1L;vZptlTgJ7)V>V})|)O68g+H{YwtenrME^A^7Peyp+*;m*P+ zkj~CTiFR6w%;-BQ25x-ez%UG+U}ogfWdQdFwtviDWn?ha+cO+(a^wcK8_jlKesqqn(`i+C?T^lia2W!1 z5L_qs^bYZI7jGC+SrIu~I32soaZS8^Gq{FX_(@qcrRQqGbbUd^lpPlTSq3so4d496WVW=%N$e)x6P*e@|0 zBql*v;XrkADVlj+>QWnbLow+9-+vqz7ByAXsDFm=K6!{f1YHwABcfU zlTgKP9K4=aqYjd)ET31s%A<&AJpY@bCp3aPCcbnP=8ZaO_Dz^Olw)=aJ0SP zTUCS_{?CleT1D79$Z-@Ce8t`{)DRgueVx{*Br}yl4iD{tdCQxo540r=gxRUdTYQLm z=M;~oF6VnlnX6TI!(s(<1*BImj5&*K>)WgUjEF`u@6S+-yvUoQTEpI*xLc1c_$(R9 zCs3(+eMI(fb)GgcqyTM0V+4DSuw|k|4kb=^PWD=+B7K^yEK)=#UQf-gf=^@6N*qcZ zqXgSPWdPzRdK>Q%p*D#0-AF>ga(|fuP~>QM|J4xe(i)H#6``aQWRlvu0ZJ)~L+_?i znL3^*?!ibpMPIYyCvV^fsouw1`g~na=PelJ9Y%j}w_gFA*6;8%X;Hw6w!X;QM-Yqr zTu+XPCSg#P^O^f=2ulG6ISp00yvE3LHo5`v4uD4fM2MXTlzNq*I#V- z*-9Zgg6SsRo~0S-Z-W2g7f?nhZ*9&CdS{ekEs7&4q^b)|c=sC^@m}019>r7xFIbE5 zkcW<4hvhg}NtmL}+odpTg)yH_>RBw*#hHBDJHe%CC%b*_v&-N;Z_OkoPx%&j{5#om zvd_64{MYR;9A1xF59WTGi^+FSHTU%0!@RE%v@TeR4Fi7k|E^9te)0gnWBS+b>#rmJ z%EoD*9mWZ_bQt|vz_wuA;$9l*h^?AC{qbkrDW>rhITWV`dxWfc4eRue({rML*(l_6 z9e@!=UOXxU5VGxiFiCoQ;EW;Ync=0sf(^_K`Mf*e?VfXM0eJUF1qVyKQBnj!g(de~ z)K&YDQ)#({cGH(@neok5aKLh%`F$J(WP6dq?vVn!(OWC6R0=N2G{*!zl~-)RBjPK9 zclpX|XFf}hPIo;bfSEFYsmt-LY=5IiLzlwOM2uh31YrU}Zz$und?OuI+2o%|(9v)= zqdx8WQ%s1dX1;*)(7zWDXmh9d8RwETWyq0DlaqA}zv9o|RbBokw4J7iWoQFTBMbTi z7{-(LCDO9u@3Ox*RyVfvZU7~~y^w6&EsQiA-BTVst zI_Mx_MU!%)tSZKlfS%(0+w1Dv>YncG6S39=_VAX+Le5=tiz>70T^XdUg#w3DXvbzY zij}$F`1V5VYFFgmK*%fkg?=|_wJ#Uft136=SULkA_Owxr%?N&*dzxSZTr1dL$@S_u z)}Wu-(~{p{-^JD^E(HzL4)qR{jHW)mEON`y3;e^)(`>g4Au7H1=+8oMT_Nz}Z-hUzgC;nL_ z5jH!qPOZ=TUx!8Pw))0HC{atc{37Aslb|QdHnGU)TX3)5T5uWYThdUPsDc?Ct14N) z=E^fR2qLLgZcDTQcCC=~e=wj62H>N#wM*c@%&Mm^L)u63`63*haSvkyI z)10m8ISi(u-Re^f=cEw#sTXb7u=n`4nvv}k>iZn)THeToe&CdWANilm{TXgHqw{4; z3WgjniqV{*oa-vZc3H=P_-Mn)zA{_aMTfjc$;(FUwWTwqf`N36EnCe?c(>ul=Ph!; z?nbe0C-e>=eyz7Z8XkR@(G&U@ViEW>`cU}(dn+w|vT*Cee$mdbdq(Y_8(GR%i>)Y$O>q>6{p-5aj)o1$_6=8p3q4%V-=)9?2C>oqg0xNqV%D$p&@OaDZin$6 zEOaCW?m^_8)VJ&7eTdS7T0dXiMX(9Q-`EaQbXlYm@-%ZXk)SHkf*uTXguia;7vUnR ztaea%Ld5WQ7>Oy8AOm#}42K-X2AjxpEW=&CAZt@^*>GC)$q|h7qPu|6Op2497>%Er z*J5kjtW5Vi9FPfen=9c~o&Dff>;N2AtQ637b}C*h(#e*B@ALGheX*bQ3=lp4nWO(8 zy!G44WV1D*v1`p^97Eyyt0wEm+ti>&&0o+K?`zoDA}}q~t=fBq{jN>KyKm*8GXfJW zLS#E9V?X0j`rPlstQW(o2$V1%7L(n&X6S)wiXPf09PT;GBXOjvz~^}G@MR1)i+bwa zYj>i(1d-L9<~g?}I|Dp01OMu}t`kgeiJTX+VJ@U&D|}c7ip64|0i7|apQut}xsJg7 zwL_@)wiAld-uj$!4`9vzhT`EJovJB3Je&!=F-Q6JaP@iy?W*ueZ(~eJ+quL8u0Iy$ z3#fmqA5phYqz2}Nhd*WU=mn*2~} zW(Im6fIEB)M^PnMvh@for4VJOaDjaci;B5SwpEwFeS|i9zrh*KDczy>Gd6LM1QYyE z9zuOnQJ@U*i11TOcbNl$)x4jb2R z>4Tt~cu%6e%Tmvc`WmFHra@L5oMhL@HU1Q1t@^Jhgcbi=Pz6a+`)Z$fc+gr@nV4@&m!3Fn)^djf!kMs}AV*w}R)j9-zM@?lW~zdSC-S!YL?Qq}qra_@Xh&@zWXz zk}@x%92GTp!lgxM8P|Z?=boGy^{u0dFB88z8{c=n4n>RYPR`*%pZ~<|d;wp>Yd?ha z855lfXzdq}EwYxOFe@umx9tHwjf4e3~)_qEAfS78+W zs#yF^GsU8xGU4F024+Qoa2w=ps|(A~qn30$#>LjQN~^o+5=aAYtDA2ZEKwSkm7>jx z{inw1Tp{`RY3nhGlK*n@g4D%I^;@6ee~TJjnO|F7m4QcJ7R-)5z;W(-tPbBlB$>64 z3%uJ)7kPZHxg$GISD9&oCRn(6o^iHrrWW$xr@fAuZ5;>+n?w zl*OdQMf?WbE^Ysng8iEk>yqEX9wC&Q#)I$Z!J;y2ka7VsMk12L@6doYeC^;gnL9DH z(mqBpYjtYIRE^?POIM-~@@b(sZ5;Y->RJ;!N%s~N+rL_a>^pMAkUMa9;6ko|+U`*6y>sxg)o=i{`3!WmnJhsNXu5$hbNZNayWAPCMwscgc! zlG{G5)oAi~rVPo}47S9jd$Gwof);&LvRxdPxKS}x$~9j2(&qln_PDu8^*g!CtmFW| zHd9Sp+WC7{10CgWGnfUpGE(Q0A&h5R9?hzNt6gf^~^QaO5ZUI|A#63F1%uZ=mVGh_xYDbBO`^S;Ob ztwbSplzX9S__udP_z;aC0 z1Kh*&F*qZ>aP;EQdA!1u0}!e3KbS0C%(I;-X`xg00olz~@l1K|?UUZdw|qp$sT{x& zNI*!1F15f*p}=nI5}6(kV(wjU@lYbIEG!_*34a2mvI_?B5s={&gc0s{-_vl9RGp87 zn#IJNo1-QcE6C?^eD$oB9n5aOY9>2qsz{>*P3iH{>s;l@xr<}%NV99jpmH@a@x8a1 zW{`|AhDV{sX_HwkMa)2*7^1^W^l`$eri@AvobDq=PLtq_RX=n!hKHGoz>2r!;O1DP zUPLUDKZPlc?gtbIVx4=lLlOxtDzgI;f4dC|7nn9d5s8`u@TFlNF_|l57Vf8~Ii+ zPJ-#aA(L(c^&>Os?wa?hUcoH>a?z2=*^`b&bf9Fu)Y|h z;~RfkE;!!Wge$Y`?rnN@s%S=o>M!Bfo^ZI)9?f~SgKL)oHuI?l{c)4M2R&0nV;R2^ zZX`IvIMtO*NHCaZ7CK9prQ1Eq&5b)5E3|H>(4`t;aJY(G4-L zsu5HUFC}2*>fXX<`frIKCZ?=kUZ?abxQ;AN7AUn$?*LMW2Z=wgx-7h&;Chp%@$zQl zzPuUYzx+<(IYPfI7n00+mSgsWm2J*6_K}xejrI)0b02GKRb}4L60}i0f~hGwjYO48 z{P3&Zz{0(;wD!eK-Y{W&Ff9@*Gc=|%RxbZ~;!su-hE5F<^IxVGT+qg@`E8& zg$|tht{s?#ykrbgVmOnU-En(QnEg#Y6CkbYI~S^*1Z3LdOxO8MzxB@H@HH!75T@(& z?ST@XsGYbqOCP+jB1)I`l1?(;+ta<($E(JDH#ayRF9^9hk^S|hfSp+*fpBLACD(>T zV3KU^cyKoT`QgA~dLBOxUgeV1WOKS;~RpZrN(kn z>yAnN%j796KSqx~g4lTVT{xH4CF2MJPg$H{wZ>3NDIoy6=n#;bCPE1@mWR=q7=!m@ zU_?wl`w9A%I*<9EgMZ^sDdG3c;R`ueYYrF%m7Vbmsez5bXOzZ8Xh51V z`tSW%BjLG1lQ4*Y#kn3xRstd}Ag)0i@Ff$*!})bRGC|$W?hab@VOcO5(E4yowx`#$ zj}rE*K+HDoPN9jzCxx$ba;giy5EwqXR|qB-1~C6#heJZkPyTLiokm(;~$q zq1kuiiOt?r_d`B;fWO1}NoTT^=AmZ2npfUasH8ptahLQ)dMabjOrT0Z$y& zl!?!Camte4wC_eGeEASH3^j(PFg*VAN+@Cy!XXeF22>2!TSUYE9OwoSm2HkLsri^e z>!(+%DhzLD&u2FdHkEbLg(3N8`FHIwoP+Kyy?9A z{H0Wpg(s*tu7icQio+L`(s+yuG{+iCWCq?_11Ki+99cFp&G*}C`EGg-L)Uf{Pp-}P z^cAu_TF_^WT7$V1%o%Ojbh_rFQ$GkoHZTv<0xmb`wRCoX5*MCWe(+dG;&)75I0R5K zm(;6OB`h1;)wb%Kqi#sg%XAO4Vt-Fud8)(|tj*~L+d1iQIodgTSP2yOvE3a>uiBqd zH!r@spTRTWC@iVDn>6T|a&U39Ab1Je@Wm=vY{FQVHJTBsPbJJ~5Z5k482T6RrSPhNwUgfm7p-E~v=49XvByM#r& z)9%3Ed{b=Q*QE|dxf<46flQOQueo3`WYWqVeXK4k1U0s7wEa6Adp(Gu62!VkRYT6n zKBKBr;?^~dfwb?|F+zeAP)0K~*iW{dCyxtWc7pM-kcn*7eyWef2yFJLKR)C%8M$^o zzE=LSR5^9Z4Qdy)!v!lnSPU8F*wca`pXD!Rqq!aS-aP|>!vHLQt0JUe(S=eXQroDA z>763TfyHfUhF*L9;wf3@WazNkv0lt1!&ryohImmC#12}mFUIFJQdtjlFh8f}X?KD= z-dq&u2wQ>nH*(tj`}U6c+h(6YpwaTViGIkLhW$7=r9iJq|6vww*iG=7)fofGBd z1Zf%IK*iCDS*oFdxq%_!vBc9VpAh`UhlV008R-o*Y1oXuiac%RJ1-L%O~G)aP*iM} zon5Z;t=e0O0DCHz+x4P3@YQ&?VC{BL z(v1P|RP2M}Cm`FJTq148zOjdsOu6KwT7z^`=UA+ilWhI6jW z{{r(ff}rX9-{x|&i)HruN5VZZaG_J%$-EPx^F|IK4!=!$M!7s{c9~n-p?bdFCZ4N9 zEOHgRS7--G^kEV2)K)c|x-M9zgL}D_RL!SM5AwZcbYHfFFaDDH{o&B$Ik$_Oy+auT-+Hm{U-*27~d&+8Vs$z!=gzMG^^RD1fXJ>8+s zHuss#ZnJL_9Qj_t*A)DM7tBK(r;Oa~hAE7$ZX)jWys56!9o{y3RkT?zEREHm0iz5Y z%)R4_=$SUNx_AZeO?X(qr;+eu`_n97zSS~pI)7A zdR&w;cy;*k1bg2%yD@uJVthOJ2linj#A|z8VoT>@wxG)Ew5SRNp_TYNEcL!Lx|pvqO4BJ!5GrUR z6VXD#p6VZOMnL+PX4Qos4{zY|iUq8#3lEo;w?*QIS(cixjr!jT4}CFrrJy#GEK!;F z%P|0hE%`p^6@EBJ^;nI;so6>`YqT4sAar8fE&?9kutmOPxc}ftfM0rw5^1NzQB}fo zYwQ$Cn0P&9=hb(^w!Nf2zVf4El_SUl+00#MjlC2a4|}`Yp4z0_*}bDDXqjU&Odeq3 zIUBo|%SP3olBu^R%TIHm%tEGvUCTgV$oeF?H>OwV?uedwu)ma{KDxXInFd37?&Rb44ixULRi|T zl6odNwY968>ldQnk1Ivzf}5=q|Mi!NG_IeG98M30T5OlD*kow=#A)G|4+TF?17fkd!_26XbVl1Xt}F1*h3 z)sEw?D7QV0L;v_qwzl@*6(qH`d6yMOI(wl&)Hd1s)RJ465xmI-Nx@X08dTMllvewonY7W*BlgXTjy8yf$b+}%LN0)i%M>;Zmg^Z+_lnrz^UH` zR1UNXz|&o#i9=sRq15hxaf`jxn+}cQ;lF4Q=2Jl-GbIgqTjhv~*n3)V|z*@9P zINB@FhrR>1Kqk6676Wf~Ppdzh=BzR*zJTwV%y0aAL^G{tNfFhfXpZ>KbluU@TVQZc zXAMY_Rv@huDHwY5Sy>GFV(V$zH($q9ysL~qDZx5{T!vz4L;!ERmFwjE)mdgnx<}u& zwl)EKwGq3m@}!LE9r7X}tHsT|05k9NgR#>5YvOs&ce}SzJgr6x1V*LPe~Tp!>Fedf z?(Yg8oJ6n*c6mT4tZ*r@vLM17Y)YC)#(Ia&kHON#)B;+wuDQO@;owQ&Y8$IuhMn|E zD~@DAgSWMG^J$6JB$wV|{Owr|m3D+|(5-V2%Af2cU&1&`Vkcov`tJ(_UbhiKIBY~k z1T}@R93DW?9Kcjx$+7syBV>`HlHFH35ThrYHy}dOX{txl&=qas3my;PuPTHmjl|>L zR))Y0UsY}5mxuie)LEV1>hsm(H7$;tWgeKM>c0{TxzY6|+-fGX>BG4_ADeY*$0}~u z=n)aJPHmm;YaE}M3IRs~CvEN(=gt#04(5qy^ZKR~di%le7F(0Uc%&l8*@Bo!YsXLN ztfSAT&a^fIWQ1YwAGftqRX1Yt@w6yphq>1h6YQEq%KCCNJ|UNzdw@S}YCM(`UoIwd zrBd%H2!AiDRPg?sP{(@dv|ZRSYIHCCh2Sy0IuidAL*9EyC~6*J~By3=d48CrAHxg*b?6v zw~7oV<0Wgk3gEq}F0zk@sJj|$08$U<8Nx}rklCh#>8lfx>RAKu!NRkZrsr-LZ2?I~ z74^z>nT^F5`mr3tMYc`@hDVKEey+;y1iLBf7e~`pvBXcKnf+2s$ByOZRlqQ67Y*bb z!(Lu3m*yd}x=s7E^2u7|)oH2ALEVqhKZrb~d7#!xjJvs!#+-gAa?=3n73YWd@BSaC zjeqE*UK5uG#0lYSL>LDA3`Rnl#NBJGRtw}zcu3N=pNgyf+@c&bG)b*%XZ&NIq=}$lcsrGnHs~iKCt`f5VqX zUkdaTJ{-k|7&e!fY1SD*T#6u2wY!|kOtH?KB*pr|j>4c%2%)M3dA9#ihm$2<# z-J2V7fO;Bguxi9J5dAZqHgeE0{@h;sM6>!PA&NZ1_WmK?xrgWCvp``CI>nj!nS4f@ zp#=ZQL&(9TNcx|Cx$3QE$q0)sV0L~1N|l4t8jEXzmTx1Iko(cf&u(y)k3q*IL0ZbBVGbnzv2ma z_#? z+u?$9MGqao>c=eKXf1)4z{;y%4Z~;qd=YfSJ9l49Gq!m0B9+LJbI7@YozEve#)l>@ z((hfM1`#n7Mq_~`l^5&HP*$<)RFE*@^r(ah1>;F3_+{KSPVj2QA4rhl{#z$0O6z0rxTn@gFF*Kb=0 z?>iD&YIc1dSV^>7&B=0`BSfznYTzyVdZ6O2JU!{ZiYvjTp4#48Tm!vR)o#yPOc(z0 z*yZ|Y@~y&sks|l54y<=B12Z^xQ%}PmAhcqf1iN1!0?cnah6`T4DDhAFMtuD(`h$w{ zXm%4Dn-O~eX}*Hkho0#H9A;{HePXU=zP4+Hy?nD)dzpvT7jsFA-7-D*yMQ#lBeJ>E zuXk(J96IL$Q#fN4M0|_3T(%Y#GPFWYslqyBLKX`Zclk)RD{-o84J>5<`;OI*5<<14 zfxCNY;|cg8b!(HPj7{VMMOBqZ*HPEsKg;< z9h#f2E|Mb|qGEFnH0?MH{=INV?t-`)uSRgY|68YbK0IJthW_{_dN3^zf zG@zSKEG0WI7?tgzFA*|QS7BtgIMWXA)*-7OnQpp%nli)td+z?TvbsUi??y!no7x$( zI5^ni{Lrm~DH^u{`g#sF&Aga^-TAm|(oN8(g@)HuS^~RgmVysiQhVA8w^Pv4!zeME z&NTk*f{qm0KSW~gNkSGAU-$76Uz=VxcKbCQk#J=WD=6b?ztAzL$bT7Wx_LzUUusTU zFJ|^x0uD>K6QEFwz?yvzRMmzecemZpQ+fJ@-1Dw%%7bjFix=}qY(yxy3m!zT-A}7I zwD}%&6pY3v_@W1+`M$9We0YVwDpC?<Q`-dvetsRs$v2S)Sids?x6O3~6$kC$*;?Y{T~)Hv zi5ho-lP*7Xn#)Blr$b5`TWGUpFi(L|2Dd83ZOg@Op&SE7F4*e0N5D>X8I8JfjUSaC zqpB922Rg!PRGwlqdcs<=T>mS&_)2@>md}hh-8aI}?v9mwvJjY)ocJztd&nq=_(bhG zns&F}>K)e-rFfgE|1wS3nNXQ>`Ty#4?c4n*Z>=Sxh$t-q&TY16Qtg4dpqlal_=p)J zF&6YyoiZ@7yyb~=O<&OpKh^{egIE zzGuizl*w>r%Uw9tBUMs4*Q>YB(O3j_&bexuEt-n;_Qjf=b zAs(W>A;jmiY826aI0m!KN+8RBuaXTlZ3e%)LrAi;%*YxL4 zY>|kQo;4>o+2;z!lL$BfZDt4C4?X~nnj^16IyC(gsPnF$lUb6JMxWE@N#&U$EG0P9 zSQ&&L-T-8FSq+r~;EMNNp;*y@@ugJ@WS)9yXNm0wq526JrqeObrcjN1kp`_CyakM? z&-MBB|L_4X07HKev(qx1nq=vf%5tWSR`J9-qF39tzihZ1au|=KZUaDt-)C2OdZUT! zySnr?K1n!I9aAu($VdaRR}Kd;;kYkB6|V^yN^k@+T?t?4u~Fd&g`rV9*Zp2|&Ue8p zsj7GQdaua?D@dY#C%HU$TY!(_fIuSOZ2Mpv9sm5Ns=;-|};8}BcG+~$vP9Jq%H|l8#@@04^_0qa4E4-s9 z0-UsvKM=X=!oGULN7x(Rqb{rY!YplN zyRq43?|-;31z7}Q903~TqyiIz;>OpQ?-JY>%v=L}iKzsHaiOzPbNNT@_4Gn#bsSU4 zV;SLl%S>XMIvfNY2C6UAm}!2n?OK|T3mARHW~#kK%dz!5rU1#dr^|o;+(xOj#`Gz~ z#^Th$rpU&63tNJM$>N6$>aeOi(&#ADJnB!4nsBKa%WB#T4rcKE{b223`)%!R`(xo< zfn14wketFu=UyoDnZgE1(IpH%xX=RI2I!#z)Vq#CHGY?-JHF}n z9V7u@Tg)KeIz({aXvQZbdz&}{6qTn93Wk*=2PzE z$l~ss5K=RxmuZwxKF)l18^*^itgSdUi2A<6GhYgAulxS(4=q7MZFXpvxuA>h{u?O| zED=N?9W4~QA#7?;!Bluwz-1(Sq!T`j%K+p*w@GQ%>1uiv1i^WVA-qr@VXlBU$&MBo z%ns|KQ8Mhj#iRrNWixZ=L;E%&W?W%rP=)hbga>vN|cp=oI~ zkOcqm3_#z{ElnVB?N`;>fT2l+3DNJW@xP=XEgGE4B#!d!$G0n~`|cldh4I@xb#Xao zmv~(ASsu4Q*F9t93VZF=#G@6-sge;@(S2ax)+k}Y*zhx4MO_PUZ}Pa`zq`em8Eq$d zzP!~ccxbSQ(W6{k;YqKAB{Bqp78$H^Z^$YXGly8mRRe2QL7MDl% zVEd57WwKmR7h8g;cI*CvNX?XahpATV&i}4WJW#ZB51E`i+B!~jxV~Gn_ni=bSR^-D zw-|~SK7n`H9vQWgsa!bEXASRs>wPK(6ACg6Bz%!#eHt7>4&BORxa9W{$W*FnY|p9A zLq!#nM(TH^EkXbY!E?+l={z;iA=7a_2Cl7J9B~f#Qm&DXOI;wYcsgwsuz2IY1@|J3zeMKVTU6A!ik*$4xOvNFw-%7#e#F--r1W_;yiK{jcDTm7ekO zbEeQW>RJEcs=N`Bux{u_W|A%o-}{I51_QnwCZT8DtJ#iJ368x6Z@*Z?8iea<9@jaJ zToS<=FFIL^adCj3mL_m=Q+Z%NZk3m#?(9U~>U1p_jBP!=uDIbcd{;n5V&96-Xrr-| zg&{YYfaflub!w`ZoLS|*ss_PB1sr1Kq)c@yFI|UFwp(hG@XWVrIQe=lHnxiI?GAF{ z4#OQ+R=DoOD`Wt<6Bs9BmJvG^Ik$bPlhb~Erc%4y-lH8;mJ5gXYBTwTZcq9=s61Lq znfYH6L@DXlVC49E&vPW3Yr!awW&EPCY!eaR+TnA5fL*a?yDRH2 z`Kb!XEH~Q(? ziDix?%ewgcE3)0NZY<_nvWxlDpLiWwGmNt*%56}VZ>AkSjpshCE|$=Crqdb&$+lP! zPGBl2tINk4PJ^e}Ai2mK`$5eOTCW+{kg|-}`qzKr^5?1%UD)#s!KUwXgCU#y5o`mD zwA*Gj!4mW1Ih)pHZFVWmXQi#}%0J`ride+(al!?`&V!iS^Ay(C#ssG2y5mlZBbfzHG4#x>h{Pp#!6v5P$hrJegqR3gPH_yJOjw`t_PRhBB?$ z^m;>hsfI)(4l5^0<)pY7X#pO${0d)p@f5FaIzdY=WS|t))ly+KCUKl8`rO_$%0S4n zM<#8V*S;UO{p|WEaL28_;L*$DOSVC85OvF{n@M+POAke2m40Mb4z! zY71Z8nPfhOkUxdW7hnf`9L8fUO-_G+!$UW=boac0d9>c>iVP=Q>lF@2rZLHz%{>U>axCE3S$Z$Cw#{wM14_IO<4jBkt=dSawkQ37O!p3;(QpJ-Q6=GUlye9l#Rh+b_k5CB|{sTQOGJ=EEes|Ke=(Iy6Dt>v}~>dbR2``LP?uHU%l zqGW6|=FlRaj&PEFH|oIHc&pN#;tZuOL{Hhakk>FdxhwQ{{9osj_2oVHELFZIr|9p_ z(9%v9U6z9Tu2kJP_iV^1p3o-<(Hg6OgC4U8DESR zE;_`8I&dTo6T$#2I_5R0RLh69cQWjpUq!@MAMb<#F-(q)qoDj;Qrh|mi6;~kM`E3T zm-6#hXZ%N~DExh!&m|-$MA1tF(z@ul=@y?UqRx8bQ? z6m4#PXJYM=`YvDpnLH%cIq=u8)+nx8YpAn@mu8fJvCr>;T*=xErQCw>3A5W+C5zG6 zdLO%AmN2q$3vY9gYBu&H&xNSSIO1NguOnw(L<#?O(@n5|*uET0#mn zl6re;y{2j;?~xPK3sJ@|7;A?dUf(PU1KS@hNl7{!wzo!)oUaeVW*Yq#{65?EtKYa;WNxbDCH>xE`wQAge3xg@;x_QS@P|V49YMX$#0U(-TWFmb!PLsaVcWtiA7Ry6Hkk}PxoyJby;MoB6}4{N34 zt!A+L#!(Y_3^uU-P3`fNRQctysS?Zt#j*Z!$z|ns1`WvNRu0Mgg-c-kVUvhU{rD1- z+LzExhZ5YB_9=a^y#+J@eqkl`7+aXr>dpl(?%KlbfJwT(qZtKi={LL1GiiGA&rU}L zRsOXao_sOd|{I(fwTpgG_jQ^LzXIzZz4|z;RVfqu|YQbOHbg)ZqluDu_zw%{p0V zlVf7(%=xxwM8rFhFg(zIon_hQSxY)Rd2=6d1SRvrfSnVZ>1gQ=0}9vi+G#(+5j^&} zBw8;n`he4(Z(wZ-gYU>kbCW9EO|hk8**}a=OMOsQ&}OXaCiKZ>LS*D@rP{sc7$`u| z9WCIv2;VEdt-T%z{BU zEiiN)eyN*HFA=lfudm{i)aS_QY8?a*&M+cTbIFfjaHB~#zyWei8vGwyZ^2d9vaJCI zNRZ$V95(J2+#$i8;O_439tZ??mkq()-EHIU9^Bo%H|L)F-s>Ly57wwvHEYh#>T~Xq z9#*Dp4UX%%Rd?{sV;+I|Y5xdFSu#K1tVN65jf_i%@Mf#+-=X{ z9Jx}!$HVJ5&yI|ChhfU;!;|5>qze`&?ylXlOetz?v$4(ee)q{#+_TcfD#_#e2f7u+awOpgE9=1}CSOGTB2?45Ki?Np3k7tJy<~7c?MANcj%k*6SnWLa zck4{5quk4gWHl5$yve3b-4=zdIjq8PSew5!P+sLnOyQH$N+;4S%5VNzRy^h?6-EkE zeL=#DRJ-iS4RI*Xy(R3scF#c*826Y|l5ersT?Z*Y-P}(C{4HT75rt2R90L>$B zlKcQumQOnx|K*iK{77Q^Wy^2s(;cDw2*N`LB0h8&VzBYFN|0^FI1Ag@d9U6kG>!LX z(E$|w6Mm1Q=F`chB8MK2N%Dl=R7wc!Yb=;1t%)xF8cpeVIZT_rwy&w;Vqq^^Y5rm> zdMvq;UWu~`aNwBi)FZSPPQ z-aFK_5RKT@iFl@2N1)=n4?Om>-ees$v#`nVSMudnNyfQncEZKHm{j8t)nmg6!L_xw zxO_Ui!1`|c2!}Zhg}od#df`&am}dX=0y^EIrmCUET*&sQ9dTZ_ULQU@eutb?8%)Rr z5KYVk5(MTW>rL-k8&3y4AgN%K0UE#h>nAOuw_tCf4AOC_3u>}zB{O*63C!oC1DOC7 zTL6x~nu+(#r~XI?r(J*QJv9r~?rRMl#g^KOSWY@iI$Ukfx;0~)D^aI%R6QNhbFaSH z9H&{L>M)~e|Ud$vrtRbyo^T+1g z26bJ8r7U_rvJ^O*DNmjgIhb(jSQ?LKez#-2O~N+mkC<~9;!Em7CvdCj9^a$ZKfWkW z{=fSm$dA4^{F6%YuNHu2EfX=ISEW|~q_*t#JGuBI7~itUa41UWrB^VHJ@*;wNDZgx?bkFJ48yg-5kNS8Ef4vZj+j~N5(_)Xu?}x+2z6h?BizI0_USi?x{^TAoWz%%3%rgtu8W>4EfYpSMkJTHlZM-+S}RElqUpFE;SzdR z``e_wqv3P>#$zsP^O;@8Yd`~-L%KiOkyN0c{ET@IgST{ExQg>7rxWRSj}J5VkD>#g z;NuW!y%@}0|F79+0>NHr5Q0c_YtXb@*^R@3A%^)ODKwGIj!UmpFZ5bW%^V>V3GG>_ zen=xR4zhT}Mdv1FaPd#1r|<-TE4HJWo0w%K^BSv_gLwWW(2zhogR^2+>cG?0n=Qlj z7=CJa+{=*L=I;+XQ*6Rf7Pz$+|-?H~HO z+}d-6;F^Ms|2{t1HDVO~(o+gfiXz&5Bu3Z)48aiWp=c7u-~F zopt)@WiNOz?Looals>|NAysXlIsCbxanhNb@cL$SReY3u#;ALPO*GO*_N38sz>8VE z!D1RBBv}mZnai1?SU%7fy^}EyOuuo`z{ZPNMSVVw63VGZ?flfL(d_NmiD@H3!-WK` z*)qB1U=t=l6UMBug&OSEX%`P?9d}JN)gH-5BGHZsFRsMT|g`fD{8D}+35t~~GCt90u`!z?uK14Dk z6-{bKtef7=kzQ0Tnh4auHOgbddRl8fZ3=ppcdMDM4Ek7ofn|ryx8KjVjR<)e@FlY7 zt4v9#@K;KYp}XKdz+M z+3+RWXGf#|v&2KNyn_Y<@}f`n#{B)?LD=ORT8pSWGr0v&AjL}ul%qxgGz^d0mWLVJ((LlU{McDAW-MB z*sPS>1Todcc9^c79GuoQ}xQ$3y zIJ#b0%cZWB%a0)ogP2yMZ=vh8P^nG(bR$#FEe6KQkuctGph9ts_Me@kM0S(9I=*~~ zdlFHdDnY<+utbxbyQl?< zmVh5*Zt=hXnG{1H$k7s%Ybc8!2a>K-)#lje50rh%Jgs%594lQ6gYvj@-jYr_ooO$i zbauYopZzP@Nws&ns?OlFMOQ;2;knMy1 zawpljJ~b78|I2`OY#)aoEo3Jd^Y28gs9Yxw8~SACOf1DFep9sO`_LodM2_O}f39=N0p_DfS8vgvA{N#xMOV56vWEGB3@>1g_lMq6`7Pp1 z0hy($hJIk5W6&VS!R-2*`%CcDp3SQ`_uE2Pow?)G@pO^wFFb~EzJt6+tF<y8(r zw&R=YRQ2ecA-?TTT!DEmMGw6#?E~75XW_>UZCxqf*A>@-hd31RCTRmf4ACH&iJFIZGtR%%XXuabSptx&s~NU7r{;AXsgKB5L+B{} zYjfAs&+?vgq%w%aPnf(tFLwg3zI%Mc92S?Pw;v(lB4TmaW2$;Zzv=Z>i9aS!%(w^a zWdM1|WinWpn_V~> zuLN~78j^MlFH}AQ_K|RrRIB5Q(0=6EBrY~%meg`VJ~{bUXFPc-s=`=!-D4J(M?&8o zhis%(#!SakjA5S!!>igZe^%UPk>gZ-YE#pGkOWNqIZsp z-n?~grq%v1^{JxUodf>rJ}BcBb^$8wRMPfglXhBD_x$y` zv=7Vt8N>9|r}?$(Y)eL&6J87{RSkr!Q*Ad=kndijI10qyhDQQY4gR10U!;_10?Qxh zbHBI(V%(7k>~vgn=vfPp!6JT2%+CbaOldiPZKoVcMq`kJbu8bE3p2|9GmnmIs6Z2l z5bYX`pj2)8eGNLrH+P+_`oaMS;MR!}(Y9L& zn=-X|G`*m)V9u-w4r>VfWQtpfov|{&6u!L8LJ<`A8w{jje|&EdP_i&${<*2_zJ5f> z0V<`j_6mkX%-4VaF=-G-65AkdL4srS6TP-G!4S``e`JcswIKLh-DNMccivu%SvW&~ zdTFTOTMuo;0nBy^_su52K3~#H@ymE)3agn{F-*TbNOF}R*7@Tuqtu;y&q>MJ8qZ@18Iq%$1nwtZ z&-4oUsG=5-*fygWJ+(krkE76}fpac7s+PQSj`Z90<&K#3b8$V1Ze@qjb%n;)b<3(T z%6&TPCmUG+QiqYR{4BQic}$rT6x)26YAt|Z&H-N#k#F^>hStzlt}=I@oH=_gW=tr} zz5$8>`QDpwp{8qx3k}1^GcEPC@dtcX`KMses&bX+K`o+(r!{40;ry za)!=V_Yo%vSW8@{fa2{At}SfzupwXWUf?EGOT@+8ACJHCnJKRa!rIh?1e*r0} z-^zwWgT%C7C>r$-3%0nFpiRR>1Sb35#+qJ}Zqsr%sg6{UFq z+}wghncL`{nRWPx_7s>@i7{th!=C+FqkFisTT-9<iho$65^&akI-U#pC&$VeC4q5M83eY6Z`Zzulp{#zEW!PEUcA3`41 z4(f8Y@Y97k?PdxjymoJ_mI(Y^$}o45#V10Bz1e1hqoxQP?Vf0L)#cKZi2f`$20UY! z*dzc8_=ym@^=>+)yL*3-$(oh&ZP&id&5iD<;fR~J&vu`m)i?b}<*Z48p@mP>&;v62g~;(sb2-O{UY$%p}k(?$w6u@mq=;t71z7xQ!=j8zd3t+ANH#keYx~) z2DDq79lkw(wKw=b+>=hse{4hFK4jkywn+p)1tu&IF;y9X(CF(E>>zT)AyeEgblcXy zM9Dt*%G|wG`!s}6a*Jm@9>vwOdK_0etxSXR$qS;S`uUx;Qkgw_iQin!eU=%kz7KG4 zTsF8z1Pq~!a<@Vkp{A{C%>u(O`G&Rf+gnl$SRAr%O(LUk`-~wk}tA*cqdlH%Ae! zdCDnhY_n%e9;1QKLO#F5aB}>h-hw{t;Dr`%vk`Ec#C^Z` zS#&vY`)8kC_)kg$8cin82D4L-Rs*fMMk6y_6)mwiz|~{-xIpUMiB`hv<0i7C3l_Vh z%gMTbM7q1j_TZPLI(XN8t%w@CcJF?y66^zn1aA&e?p^)cRYQ=<6dU8*wS+8*tYcRE z&(6Q7V9GwAzg+wuvew~aYO04#AAg?!pZZuX*FwcIS}ILS$hJHV?#F~^V*?p<5t0jg z{z&FY`PV$JehwVruZtIoL3xVfY00u&rUn{LLkmuYu0PM8vG`@{9HmS(+$#vUzL=Mq zpZex4OZHj_p%);_UCC_|u&iX^L?C`yVRM9a&2c(DldvRQ0;;SpXP!2hbFf>U@1upH z@pScTgA;m0Jv~#WY*x&B#&I)#8z)aBy_378{O&$LetkhZzS{siIw*zzK2An!Y_BwqVE<$w7OgfAb4C8UJ$h0d8;QZZ=o&i?3- z>hX{jv7eXPwUS)EvzB<`zRd?~!R^PSq*D3yBTwt)=>p~Z;4bI4g2cXu5o2E==79Fa zgK;{f8cL+>qvgr`}&R;bY-a+{ZrUJpfo-T?X{JC(1)dRy$B3}!+sh`_Zk+FmeiRT z3IVf`i&79&JuB0yzbWYD+dPD)|Z`$mw;ak)Loh` z_FQtNltE_KR~vEq@Ii<~O~7hOYNN1<=3+;)T;)6+*Q@+>L?yjzI`#If#3q>OO~04Y zWUzojD55)wweA4nVT9H5A8I{{^y7Jl>0+)s{tJH)C_ydbAy|N(26sUrqR#WLn*}p| z4JQoKWphtx{=S{nNqvI(U^qG(GhQ%4MNf7QD(Zl)dV?G41u-CHh~IzG%b)eS+D=-g zL1bNqGDU`qmRTV`mU6W5Aj}*cuc-^34?Br~HJ4_)%jz}{CnIp)WjqF-5p4cYOiN>? zwg1K>K(|5pAX70=ZT&b|AdlH#%wKcXf$liu|vvBCy|>}FpJ22MC@ zW&>5ddXPXyJv*ZSHbsHBp~rCPwO_2Vlq2oJg^lk%t-n+xesAQ%6r=LJZ)PbbR(~Bv zF?i-|%17%{`csS+WqMHmE8Pz zlgPca^3u}fDurZW;FkK@NSI)S;j5CM?!d4Fwm`^n+(0)p-LSd1;{pZIn|-U|v$>;5TCPRWzlvw&TLNAN+&M9PFLz=kXUWB`Y|JiQks zJ??vQ?DY66?ulST%4?o}ISDNCs(0E$JawKzZ3a7wCV*yTo zxMZB2DFR{ICPa_!>x2clVZ0^PMmgoLCzq#usR(mg9lZ8fJeK>?-V7EzJ1jZdm9&%g z3Cf}%0C}u13>;IKKG?xyhJo1|`!Q)gIXV&l;W9%+8lmQG4Re&(pYCRW-w9XHM%OUr z%kb|58aRp=YA`yegaYou02m8|SfZZCU3i)`-ffW!*?K#c-d3Z3Sz6FNmy^AZd{9E}aE5 zkdZlV8qn@RStueGP3iG^L;g8ECx-u`%1$2Zovz+eWAYL?yDiG(Q{3Plx_^7%$mTm1 zRrx!KE!`rYFEp#{L5vrx;6lm6(ms{>eQfc6ms5W<6UqJ8*#rHpv!;3>PQHF8mrxg0;H(ppyZ8JKlvxPYRyVC^ovoRagYH!C6 z4vBw_-R%}vpdjWFj};*F4FwwG-4wbdxKJ_Lx7l!-O;08)Nw`8-@66$rI)&=3@@>}R zq9RF|by|-prulJRU;V`CFJ+U=XOTJ;!Wgw38=Do;D1?NLDo&uG~q`{dn3_i6S32fFid5J9&M+k zZTqr85Skhr#>+Rxl|GR$JZ@Z4kiQMksc4NduuGh?Sg+9Sbtmz-w}WuJmh6bJt{r-h z=k}1rE*N*U7dg^qSq0Y);{DqlOcA^2cm5Uh{I{w41VQB2Aihn2yTdkMom@$@QPn!| zm|4n1&0PMOH;-%V$ydO^0hg!hLPm1tzoF9zZiX26AeeWL!5jT5+R9mok)7{1+50CNU`ofvi zi|rfeF5ufFDGoU74*INV_r+q9cE}-W6o`a-bsv6bN1F~~hyx#IP9!Kmxk(*KBUrbC ziZbKJ({#Vg2!Rf!GM|2!zw2?q&UwKs7~fULn|X~hHW^K;DxyJh?zu;`j#0h{Gn^@c7+V^AX~N`nnr*9)Q&i5u$O>QdA~-D{Ourx7QsDD6|*7a zkCbiBLMaq_gRrt*3?MW|DDvF*uo0)IuJ01I9nM0x9{`s+N23D5;ayB@F+*Lj>cx(q zPpuS9%Ysv_J@|evL}QvwBb6Zf{qOmEqZAya9`heZ<0pRz6ur&fk-)*wSQ*m7vh-ge zh(2^x7i7ckg4$M{!oCL?EmN)nZ4?*IYl-2?O~@zb$0=bxu8HjC8o|nVG{oZ`^Yvr3 z&*glaVkQ*?tgQyVOU+fJH!hmhB7jq%@J0qFvMW21`~8qLh29F;!BIti&O^X2f{PTB zZo@0^3uWABIWtSra3waPh)RTAS%a!tO6ad<;kfDJsoQ0&^E)+|w%fKYMYaFOfonyq zQW~fn_yrjjQ@1IS7Vr~5T-%<>;|Qp6USC0Ocjg3_54u)@5ZBfproDUTe$_XKh(k34 z{4aWT-?aAdL14>Y-)=cGbg@ zX9T*4FnM;nFgFQF@AmC4SbI>xxFn7NQI%wShS2d~^5ZM#`;7M2j#lAc>UP5K!xW|o z6P5Io(zn7@ySO+2SdpDPv7ZD>4JT#H7f+U_2#M14_$us=JS)M_rveU``a zm|-fEFxc|;yA z|B)_}73CfT`rQiQa%jI%3>D+#%iXv^RT>tr+P^N+y+*7I&4mWUWrT^z+AE7j#rtN- zNz-I$>C``fuUD9t|DaI+T$@CGHyI^-VVfcJH@^!~Qs+I(_fdrgbnECTGsgZB>IhCPV$ zXI>7<%?CVbXqN+8@qX38z58Gpd!Fjs?4RBS;o0G_GJ*q;t&OC>+iPO2Av(Zb0j+N+ zmdb_RJ~!S}EAz|g`f}XbTaf1zSYo@wZprg0 zn8>99PqkeOij;^@Xvwm_x$s50q2w&Jb_x1?3DaI>nuP<@MlT9TvJkks}132l8zxbeNz}D;$FNAI*6Lm zaCh_iaimbug|X%Z)|MOh-^;>+l2^G%ODGk!9AoQi>=3l~Sc7_G1upE-&@2210~i!r zf)A>K=b3(IYZd02Px zn^G2UfKp;1FV^?t8yh&amfRRq^h6YIB!)jnQKo`pmM}mg%6$kEyLYRx@o87U{4_0F z5(p`f#u98yJN)!~fbO$R9G{&2FzBZ7zcSYUz4gRC6n(x*9B^4sT1E#*8TRUhprm%$ zK17l$5^;P9zj%&MoXfv%ZeWx&<#)9J?1DySb=83FZ@QL>s=?GDqlnBR2E`FymMR`h zBv&A~Y~WP-lrF?#DRK6H^Gwnc3y~zgR9OAV^Np|Cdl@y*(iYa{&?R^K6ZFzKn_;@M zUOnV_BV$SSd#-1e(&{x170BHoe|#6@BFkz}PDUPr?clzyP;`Ysc z1BINxIq&Miz}n#)DI1=KT`T*M83J}?ze-N+L6PC(G_T!!=0rWgnjx6*VfKsxBRnG1 zDAHGDT$!jo`pX<)Izt|-qRDFr+;!%}e%oD&4BUkaZKw0k6+ z@ZvdorEe|OLPP7_b7&j;*W+x*^w0oIRac|OTT6O!8her6OOq<6PD2J)e$cOV|! z8UmwMKL%q}VhxsM`=s!-eSOp1)pVMhsmz!5b{>PM!8EOjO({rt(_Oz>YNVi-A*$u{=P$D$e5 zRsOMiw%Tc55WnH9FJm~!$3>h_we75~LWmT>{OcbCg)NU_ zf1XYN9+WXE*LHb`3trC5sCHeQt{~`8aosrsIA2*;I9-U<3USEtFY5C8F7uNGv z{c3xM=yA*8U{;sS>HdPfj(m?#dr%E$(uD5IZRT%)8ucVa1`G|e;Ky!$=-EX0M6;Nj zYTH3V<6JSHB@WYTNd}3eC#u!fzlzFD88<`7UkBTRjHj=gGKt1}9HbligKgTo;c!z?F$98Dnyif+;2ZK0NR&I{gsO6q@f(;}U7 zR&?8Sc|4~cUy4Ncnyl!QSf zAtc}G%_uq&i}42VwOzu`D_?Rf98&_Eo)OvSMrL@ddtIzw)TuH86P44av3%EmORh9Z#`-_);a&HCM9ewP1@l(O@Po-b& zLy79a!u!#(S47LGK{Jw1T^7y4ze;z72q+|(7f0uPE^$#9>jFzH;2wVk{DYH$e{?6< zhc9H=_zLnETf(%|onD0TzZmc}_}<{mOClw#h+cpcsW?MzKL4t1?)oH)reG;$naXaH zkJ#jcIM5zQE_L!O_NFFZKqtXLEvgEI6*)*65hnn$Hi$K~6Pi?$iHZ~9sHwIIR|i>H ztXPjE|5%M^fe3~lmOCi0-%ILhrZF%y>@skd#JKaGQEnWcu(`6mUKcgb1;fcP#9$>y zBti$te=GMlA~bZLp1Cv4c5J1g8CX>^F{2{1<~3F{*I8beG&Xsk%LHfhQ6ZOPy9p(h zyW)eq_GVe`{6MKiReA|{auTLR!R+!>UWr&^h2v|Qmz~0d;2OfM5o+W1T~pC;j{tf0 z#tV#(B|@1_<8pY|y$lpfMeJi{0au@K26NWM+U^d>b`IopBcUDt;ric=2~jwZ3uPPK z1V>ftH~PK2y?waSMx`|D))^e1lvg%kKmYXiKG4(FnYX#)k( zuNh79(T8N-&XsXp-wb&?r37I_<;Et3+OUr#j^sSpT@3U_a6F+F>)EQDFcseLs0@Z8 zZb=HjDF{``$Lqak3=gtYDi9gQk zLQ*>8hB7L=exx^bZSC0x{0^*`;Dl?T^yt>jlsde1SmP*UYJMa8FxzUel5noJk*zuW^Kk4)sj{aLcA*psYFtp+7Hi3fTv&gsE6{X~Fkmx-ZNl_%buYQ& zDBe4H_(6QZ6?l{|+Q-C{cNy_2-#+wnr2jJ5g`jN;fB?DEk^MiwI5>9%6@rXDMq0$W zDbafvlrBD)M5*`%dN7uAXl`fH=kcn*_=Q8e4mC>Pp&^3J-J-I0F#gwSmdF&D$b`gG zOn3|hKljFo|F(DX2-{nPq)*K+{+Is#cy;4)Zbq8hU;d*j_vD7v3>i1uqqGC|rKddf z$#3NXkL;bapXnq!$d8WaosHJJ23>Z1-AKs)*Z&v#Vcw8qzi!iisa`(IO%RiC{D?zB zYn%T)NtOI}(u0QZV%qTke&iZSsWo1eB}RCnwx+y?-3v(m5tHM~ zTdyvRVAWa^WE;528KK4-BJ(+>#`~@>{2Z6W1igKN{UCqPi^(3Z#!dSB(gsF)yimVE z$*?00HU}U|bNdeY0THWaW@aWf|LT~45zn=pO}BSE3}aZV)Hc!U1KB8!_R`1|2aJR6 z>8@1c3qbc{%^Qh{6F(f%W zIYkF3^*}E($y-XcD-dD2uyj!U@5T%BV_ZW8zmj&^lG#7Sut%4vmhNoJGVy59M-%gZ3wBLETR8@%vmaQ= zMWnvrvtu#~F$uIS6`GsZixs-o+sb(&A6Gqu6~!XU_jFs!holDKTtPi(rZqgsQ4Q=j zAvyQ3_ZJcwiI)&O2$h59k>Ch0Zp>vh5c(bz^xjBci*=%5NO|$vlElG^8gKRpgW4xI ziDGf9-C}caU$~?|c0Y`FUObb}{HD=}P!mwN0#s)RJj7#lbQaGIZckQlE=30&3@Rqc zZM~(2OX(L=c_RXmNX0f`;Mw?V4hH`yaS^(O(_36g)8X3@Y;>Gf!tEJ%?^v{67V*2^ z#Td*IaOdaxP*pvcT|4}Tum0qpE%r@{pP~KQk;~R>nzU>dmbh6+gkf$oq~ZmYrSRKD zZ^d?aosZ1P&1wy4aj)WkvjD2pg}B|#d5%lYs#%SLp|U?2pUO z5-U@JG2F%xXj&>=Y(Mx`Z*(~qK^Ed%%k`)JnB$U~DaYE!LWHltSev23=UlQj7C9}x zq5c8Y$@r)Ay3q$|Njl5+U4J|3?FiLp=~qGiW8n^9>p@Wrxdt#`>1CUi(3%%pukwGe zT7{1W@Esaaq*=23T05aLP)$%DFb1u4b5NKCF+sr{N+# z=6zVX`5?q_`w+!ul?=+_B7j=@0g2onG29;>eN(KNBhLtizl>$IwMMY!_ix1z@KSV~ zmP&q-uQsghN!0QaQy28AK{k*V%QlNBlNdv2LEIlOaV)E?_27pR#~VFarbi zNbv8prMl0xk>m{WD{WBlTLEA+f<)SmJYaUvR%m=)9Mhp#Q#=N1buOzwDwEsU~LC-n>eD9hgQj7R_^W)vM@ z0$y``m9Q^b?ZNWM{p08Ii*QU-q#siJuw!?5X#xq9_ubPzJTJpX{7JHDuK8vNFcJ1Y znUI8LM4r?YAo@H^gh6KOn@ZvRL~7)6A{29Grh2&Qqm+@Vw{jVtDvtw}9kH0wd@4Vm zc$TN5&R!y|(!4)Q1_DwB`CsQxf|IJrebmdX9(#|N=hz27V>Ky)r^xT5dX!%gHCq4Y zrud$P11InL8Z^kxnw;g11PVmS(3z1bg-9lMuSXSpPTu|<)a(_4QS-@z_#?^Nr;4ZfexLoI9?AV5F#!rnLy=DAfXVU)-N7`8(EjJZfZn5K z&b7EbM)$)w8y&R~4YDANs3*0W_0o?@=n1lM?A~pu2%u+Il8@VxdIrvNa^6s~Nbyqt z)?r={?AJ%uWVjKoj%Go zUtg={6)^HAK~JfNjZN;o-niy5u09`JPkXi}-8vt$;V?Jyt1x#b2Shqe2`nPxLLt0O zn$o^(>4)P5mmst33JBBJH=`+SiONc2;lKU7?w;0(iA=M1VDp>n_qlAZwH`Ma{2@<7 z@x1a)@fN=Q&-8o;-Hw+LO4RPqLyE+hrlSKg!tByB9xrXi&(4kaF$4*@qWiyN$O}rP zz;Ob~u2cUMV24@S%7VFRv`a%QMD8@$kCIofmiYQ22kmk)VuV^)-LMo!pvqs6K3gIs z(KsnT(r4~*az^xX-s8hAQls{$0E_aosB1x1031nJYzsy1Zc|j1RX{kXfA591c~VO_ zBf0(n-dUf}Y*0oOG~@y_Qt_yHd371lX!^HJ==?#1vb-INhx$>Eb5li#7j?CDidomO z(srHH&z44be3RO6YPtRX=>VwnGU&A1T~m{|Ws;e`b~EKG?Ry25m5w57CzUtdJsl(( zbL4~Fhu2|*6uBRA7BKKIv+3w)Wc{&UL&lJ#zGj=-)BeUxOG9Oh>dPNFxB{kRHf?vm z2i;Ok9sY`P_I^%9&b56x48vH2DjfNmX+g$o$BtY2Nu#G}7bW%B_&Gz{CnztqHOAty zOY!h$r#@J_X$cOfC)Hn{oSWgvi-M$!iztx>UaaRbVD9e1Qf#;?IzFqT#s4!T$Z>uo zX$k)_P=mF6e;!B`nvqJPt>gu?tbspn5E$||@;x9T*; zad^U;^!bv0&;w@XJwlTqgNT3M<-72*U-ttaSDoMh0gpX<&&J@m;YdqybGM`4PHPwA zv%$SGJo5#*6;1T?t4_We9o{k;!2w^_qIfKS6mr6ONtr}TGc-ERIHWfBTh0n zzh_tViuy@JkaN{Z_hWs+GfY&(t7)YR_!C%!91%bw(J)9;M3Ml7c&N92hFdh9N4m}8 zOSq9r@R+D|A8bSI{W=6RFGDO}% z5t8Uj6nWH!1zRli5rk|Pe=mwn5=^I8?*B$jE4zA8ueM*BL~KXIwV%5oC?%I449Hx< z6P4UA)I%DWRN50SXSHOLp3)G~fJ3=-%?bZ)DR@ZWo{@d%6Z=}5vqzOLrNF;qKQ?mg zw4>$W5`#8!pc4fic+*fr8HLSvS#e%=uBqptw#u8>Xw0G2j&L>#&Y}b>rvH#;Jwrcy zw%idCcZh!y0Ui*+a^*pwqXRIfNGO>@2b!t$KLMvCk{X2J_O^cj-dI7K*uh z5T)$;ps1&{2Nk0*?0yjJkoB0{+xnCc-m+mG#_)s`;W$qpxgqgCF|H*?4Nl~bRQdl& z39NAX7&^*tIc@zIzU&33ogZT0u_igxN#NkDYaT#j!T*bLX9O1#EbV;n*s(o6(y64Z zsJ|rCHh3?e=lA;+Veza;6W7$+)Z|uDR)|Q_#STA|)-B#p3C}QQo%em=r z?=9@*#_gsjYE(@+R5G_)8*G|RUT-0T*!Qc-Gs|o7U9VS~9+M8{5;*dfRaeq)T~BI~ z5zB(GW<5tJAo4=*@QWj;L1?5|WNuu$9s?GC`y#&QDzQUa9`vAGlpK`50l2`N(SHSV zE+3iir!L#nJ~4paMi4d2_B(8P&l6P*r+3q2+W3HLQ+3ZUuDY4rk)1P!lpgLD{1wh! zT-0@~3~YC=42fm(PiY1-*~q@*F#(LGA|4hA1hA99#H1~ntm1VG=(%>l%G2}9c1>W z`?dfVO#oX$NAq3i1MzP^asKZg$!L81NT{;z$pZt)$x>3z-h4~e981QvA8$Ph3V^BZXGMxPHa9D$iq1!wo=+yk!8&&X3+Y9u zDkp!>G9PY{8IyV5p7usEd@q_D*Y96b98I;aXPa0$t^^!a_ZbM1($`t$yQzo@8;~k- z_VnuEuekd)lqH$NUM*~ZpPC8OOi?O6(2c5$Gb)XF32NV-Ub*hd6IG%?TFsRPt}$E5 zc@C)q8;@#`JwttVfZ%w~j$j7X{_)W>`{%kl?#(GfAj=(uw{<7Xp6l`beUTi`(^1dk z(fnoDGE(z}j;9TuNff>2X6&>u+whrjcrowfzPVyTFt4ihnkDwUc)9QtZA-;LJz@VYLNtB!eZ`7*+jKhfgB zDL0f3dE7?+V5WMAmx(xEJs8$`bCk#MX^B9vM3R&!>Am_h`gS~@x3Kr8A$*cp(urEB z&EKaACmjo~#F>4gi9gp{UXi!I43E02yNAl!zNCKCApC&iimqs({k+DYNURKG-?kuD zX4O7R0|Wc*10;%?805}nnI9L=zC}fSI+XWFGmn-lTt+e$?lgDscDR1qvzuH-*B24V z%QMFohkLV{YRUAYjZ0d_`3yQ>%F{Ckf6g4*^o#1c$4%1_ft?UoBjhHts$O$8_p*1p zZ2IP)(|Mw$$6<)6z+18@!K>kmLx);Nzh~5C&Mnl;E0Oo!cEIlK0<-IJ zP1JoR0|{{U(_i?*r_0-n8-=OSLYi zwzDI{YxR56TJBZRM+yw>v)9`h+HsF5GJ;1U@NWGF}u0^eBTPR6(ZfE9VF0hZ|sE+}w5VaZ}%5Vn6j#tPK=~o|s4q z&j&pH;luR+kmS7@jQ21O@oVH=S?Z=3IqzJ_U$De#C-mulEE&B$+cwtjC%ieQx0NY3 zx9zFdA;1XRuN3QmH+uqor*R1U@Dco3Y~)#G;iy+#L`Eaj&Y2NS)a8E{bxo2QeLOwv zfUikM?oIjgDLKoP)Ss)9GNn>1Lsbx2l95>%g`>Eas5c8#pN&rOnxXnPV=qb^ObMHU zMN_U0YZXDX{Y-KN-unr`2C8TXc&b>XlnPwjBn`2i1WIHI-bDaM8yqv71{mca{12ZQ z7ZcyKU$f)g>pyfmYf}zTUDlOxSr6hfi$$WS;X<%mCW0(yYvnu-)fr!QOz}OI68Rch z1(Vuyu$*}XTnP?VHRppoHC@La_`rM0bG)gAfA1W4*lg#|z`Q=`eALoRr&T%}uojy6 z6ti7WteY1W)@xjldG^5PoUvG()4uJt_d3;)$o(uIp|bwk{Z2!b{N?>WM*3CY=_K|F7H9&E`rvVpxv-ewIX-pEVi7RrcwNA)Hiw|W z1GJwpmxkp)pDXH%anw|;*{Q7gFU)J_ekny@WAT{I?ibU86?9mb{Xbomyy`>-Hhs>` zJYT{TkU!%-h@}_KyWM7^EjDUQ72gi&>}AMM4KbXQ>7K04Cj8bNCwcA6ujR&Sc%TD( z5e}XFFHp#EJHwjV)^4|M z6%`Q$6{RW=Iz&J~YE)EuFQKDUrAi6CY-|XKNa$Upcj-L=={3@OA_NF61Og!mA#kI6 zf9E^r-t+7JpWx#+=J~wbwH|Vhh3+I`gIQob1&J+fMKwn{x)k4=2S4 zHsByto_O^%FQJj(%wV@MP)0rw!*y^gCpFwiAn_bLTk^wt07s}~=ymvLjKWsqs_1E1 z;uGzR3f)WAb(KE#KIcyWbvRv7SQM9XeP-}eMNd@@jM0|g+cx#{-%P8S0)%T6sTITI z*B0|D(~cYgCmDNz3Tkk4kJv<>7!!Ex?2_sX$1Rq~S3l_u5R!?!{rl5aE|OxhSs^X^YQjKcwppvc@{8ABYsDEJiHwbf`dJ1qWx|(B?Q|g}2d z10Hg2^(JF#HS(}ceIwsK16M#=khdT7I0Kymq~XHaZW21e@S{Bo(rHM;2P4dF>Z5z+aT1vcbVmjWr(IZ;Py`FbB3OtIL$LdKh)^%PCRJ=43o{=%71jaYNJUkc`JurT`@jUL}kdz?ltkWKVMzRvl3%A1(i$}zA`%o8Qx?n+A-FJhHrvnS}wa6@yq(xb{670u0MwwsXc9d^lNV!r~@4XKbMpL1nc zaiYCrC$L)YRw^cNC>&7MJ0RX4Wcn1h|NZOXo_|Wr0BMjL-!DE%>X&|RWO5yt%}<3I zC>EEnfyW8YIH+Fd=<$rGTX2Db%;Ap$K@hSS$xCAL%Y$bx&rFw^vQ|h`piEvjs_VusN^ZYl7R;h4#;rF>`BpsScBQVPDUd#$|YIr`@IC8;gvqh83vBP5AuqMg+I>>{hfRck7% zLAS4>+ytiBRsDRHp(R)AgP3iXf$S$G0g*6Q%TcgXf?w%fdJfJzf1EmBDjC*rAHx?` zF<354XW2ABm?6h+HpHyxTX7B;!GwC)^;YQ-fwEjZm#*%qL2P}qh28BRt<4u$^8FZ6 zd5#Vr0Z>mc!*lz`jl?K z{6IV-)b5%zpnkAg?tGf2``wTqNRw0T({!BbkY*y!nZ&1m4W;Jon#o{w zT;(@4&sMk0Kg3mGcQ##TY%#9HWR#~l)*Gcc712?98)RrXHAQ%U0##qM#|#gwtZ-Y} z!0wzlu1E2~^MS4>qw@!ilG-Sr1OJjMr&wq~zODK|=6QUVwwQXqjlc~hNcJ*kMXa}L zM3?@E3qb?E`_^s6(CxI~vCU!*GDt=AhPIUuq@>cuj^dU*-UW;*HR+w`GGn+J4h^jZ zL%TG=9c~zv=lh6|IohjL^lpS|X|6wRCNz`&q>MA(EHWhorABGVKyU%YoKbdUP7!JP zqV`6K_}E*`BHv=f6OQOY^J_PZIAu4M{*rgT_|J3UITdUj>fgcbkS7tomYO+r0_}UI zG_V=!aMK_H6P8@L5%gv61>%BQ|Dw*`qd`Zx!>roxS;gdFSIg#rW?Alg!B$6Wx*C1L z0o-4ljG;ATm84Y;qJ4`e_rhSU9lQ9}ytGe({lnw!hz9{HHgJ8sLaH3#6IMfi8v&CD zVQUbV+y7b27CC2=muSKme~|9np<{GUI4fMTy4=Xu;Py8Q8tfKHnwOgk_M9_C>Gd*# zO|MWt&AE9Kf{D|chjRo>4KY0pzWs?TO4(qoG(s`AAdWsYmd%bY6wlGuuA<12mdYXd z!qY3|o5rfar*`LkG4;5kVMLv^=SV@lWzhQRp7h^1mOISiT9-cm4ITT|uh%tA75c0# zPeY>esxteG1v_neA|ZOq#dF@7YQ;v(ca+I2 z3m?$18gTUjOGt48r;>&uXPGX&xs=*#o?q~5=1Q-Ir#!f8>kEC5%hR<;C3XI)2?Bgy zYA-_0dnTz8J3cog3{hHpZz>|s_#8bv|FM@`C|S6t=72s52u>M#om|8Z$0o54NK5KE zEWC#*$|Vdo4>~^adZ83&_S|qkswWlRQ*ma9d+ZYBz8Cs-0K>H4)4{5=8#HY`1yUvr!=yY>SDSWY z7N^BpM|7Pq<)QT3moBoA>8FPCNMZ-BK@h#$w1o5Ene-VOm_YV`w=M>rEHGZK;X5p|B7w9* zC}v$ksEG3{2XV8Ab9K!x#+5c$ZbP`gw1gmgwylvs5Nf$0l{%)OaUipys87v}XmFv%uKwoMx_}KTJ znV=!$RbJ*}1JnCmkAM(mW%}F1p6@TdphQh<+gUF6bC+s-se8gZ<3F_G_b1DfY=03l z(C}Ncz=Tpz0}^tH{c#$B=Q+mmhS#J3y~JrYR z7X<19W8Y5=!X-s3A0?SPzXmW~`Rk7}lNk@J;)0`nb{dA0HmT1pN37q~A9(r20yEZN z$Kg7G6diFcV4`?}J6{>d^+#|ofzPtdJ`ErKIubQm!&3LC zU%hRUxIlv+K#rC~|N4tX&0aJ?gA4erf0nOiL#Q{ve$nWLg8f5DljZm>W7F$T zT4!fW{j>vRAyG1qEK1qZxQ$RvZo_L++AMCHfGlRd_{Lv!YgFme3h#Jv^$v4f%$C#9aQ%35Q#yq30rWxE zgwf8Nlg?kRN_jOPkK|dtArNP6KeGzD2~Q|xbAOXteLp7gd@{MOn757RjPV0n4ZGgf zag8=zf1EjY#&bmSg=xbpm$@8dnXNj2<^B`*Z}#85wVLbr*N(?G-7JX`_4N}sM#_pr zZufUt+{_PFJV56TUv_`nd!NNBBgSOmow42o`A2hRU3II?{p0VWXFsieKnV(}mjPdZ z&5PrMAd?MhX+Mf1k56}nyfm+xJGME+)_*I{y-${wvS`&_@Ra2jbU-?ovg)aD5WvX- zapfKF#5ZO7C;FEx{ME~exbbJRix$WqnfL8W&TLWHph}VD9`56{oJo1bEE#IoD1&Fd za*BJ&>&vy1U48eyF7VIjva9JNtcfJb%J}XsWT?FxdPFCSV)1=v^>$ZbW-UB{Euc*! z{i|uv&iSyFt3TzP6MGnhe}`G&nL5OJlv~_;A^V}xz(uZ$W|MyQIcK3P681b@@AEPT zDlIe<-WlF}tMy{me@h^@m9j8ye+8B!UIn!hi2p&`^X&U*v1)enN4IsuGvVMAUo~6( zqtquuu0Mre$L|u+1G)16gS`>|`rbNu4ECzg<-?696BA$EV`HQVPtu`@qC#oyHuIT^ z-J<=%kZ=;i_i#qezcUo}LZ>1WO8`%8$PGN+N!AX+9o!xrk5Ae`ofu?_y{tN>cX$Ryhc)N{hH+b6Fl%^> zJh&;#kX%^wxKz_-CmTa*J?i-g@}mEf?(Ty?QL+B%aiyOusem5+p$GomS5Is3wPi?Y zJbi!a_p|%R{nK8v{%+K}=-SzLdX>eS=?+)pWAo-K{Q# z=M(vUVjswDdwR2*FKQ*SO#ENv5ecCWFHlCA%*4fWgPqMsa5xelm* z4=cgm#64g$HzT7|fwd;b}By$sLPR0<^$K_jkWi!N3}lo?O@3$SZZ}m zihfm@tU= z%qjcmcTNnrx>E}mwa*j`{Vqq*2UHLyajQJm=z+6}^9>6=MdsxvT^((F?ANI0nan0& zvp4wZF*(J~?p@%SD}r8`b3s;tkbStDQcd(MzC3dfu>t6LxAz<}wrqmssk2RBQtD97 zOo9#apc`d^gpy{p`UK{Q)3HXK(SJT<7*96=SZLi2b5oG%m~k6K5NSP~i-> zzTC4K^)|Z_{R4HQbw9m`jm29xXHS<6?EWev@6L=SF>X;~wKRxWiImK};PdNhjm})A{cM*^;$l(RLii zkufzfT{&B}H>FMtfd}@x?D^|I#GS5CZrM!;=)U`hy7~+p+;Ev1bWRn;2W($TRl8F` zT?qP0?-!^@%w^6ed`G6dhhidUnWzU)HW8okOTLCjAFgvDFE)>V^6xx$;`SL^;ZmZ5 z4X|94S7D21IxOyc{csq-!7dFavYNQ^8EDgRV5ncd7@(49e+>-8_9v`bZMkz99vxn z7SET3o80riimr3ZW_C`0^+S}iq?HA<@JQ{ju~7JUNjy;x5YAfNEfj7u)>T|m_l`#P zDwrS$&x1|ZPHw~YxqDOt+m&w__YI-h55Ov>J!|pm-i<|?=XNy)+x3sq52W)k58||7_jZdc?d7JZV&6u;$u3!4%#bCc6}AL%ua3$7hE>jtB|c-nyd-;~vV06{+gH^n4=`$Z_1*TxO4%B9^$B_a(LQQYsU zOoT8Y`~ELW3_BvO2U}sAY;R5rB)*z_wRI#pxnNVN!HX?3Rqw8f+#4WeL`<{c(78;ME$RqeH?o!=o4Yx5q&?NRP`!I*7?0bPstIrB7dfFZAryvBz-q7olV$m zqIAlBRV-mc=H>(`8yrA=aeOsfqx4V}>;y!_)))eb>~B$D&SG3wqLDl5 zzBg*NZ${ZrIjFqFJ+M^k@OhEv8Fv361`smIlgsI1ZPOKjE!&hnsprBZLpCX$U3Vye zB^JsL=dFRBo~?mlmlt%9qxF(%FgSYVPMHVY?pQC(>DEZRCHvoZ6@8%-V#cy`b}tQx zIZO&J#`HuqZ<2d2bnR&g+atZ^PE&^6Q%dB(3qefG+9dq7tB%ienq<7^e++S8)iNj_ zH4let2b*ORUtES~yqRq2_KfSXzNUyFbmJ@nTG&Us#5;@$H>-Q>z=ugxy`n$YJU?zr z%o@w|WX+d3gvuU&brz*Rds_HcX749G|CQOFjWO>$9u<4vk5`E{EAWO)*w1^enI|<7A=+?Fd?Jig#a#)b#tMMg4Lt5;Ez&fMZ$;FGQ_ zMdJ!Nld^$_{1c_0l)kEkE(UxtN$TlsWQ9Ohz@7=gHkRv0n$rG&-#znp+>b4W(z;%GU?{ zT=?QuQaIpac)TaSnyexJ$nZA``!&X25&b(W|9}1YOosO}SN|MCcwKh5cTH7j2uJ&X zchII#^lCYNuk`xiq3Dct!y~Tb-_!Y@m%97@59_-M=LSEfF&ZA(I$b`gifpuK-fgz1 z2b-7Q@%b;eBce^i`dzoM+N>}hhxt{+(73N5#}+^2$gPaX2-=o;b@{&y#K-qEapDgu zaY;nSte`z}y0Zl%*TsA3zYJUPw0Qs~{Wr?^&s(KU(BS7kxZb4RIWoG4WvG7xsE;RU zr%Up(s0#d-`*eA5dfv49R8~zYo>T5PcA2|x21-;rHu8jZqGa|=E`(w=uKd5>3yXeC z8(#Fj3xnrE@diKdaW!%(cI)#|%(8Ipc3qOX5^rPT+rJCwe_XTe+#OnApY#73@KQ+` z12>sNmgmSg9*Gy4PaMv9dy}vC?S1Y3^S=z2)?Zd#m59xhtCjXX`l1T?f>pX|Cx_xXy$3K@^m1Y(N;O;FU&J#Q1E7wg70K0*XBgz{xezp z-YNr|`?rt6I(j`uOrh>Ww%vjw|Nd0ME}XF{gc+0^EuK+q^3gg?CnyRR+hJ?8j7j-w517DkICD5(T2W z*(AwF(ehS&86Z|kd|YvB=w)er|2QbY518&TTC&UTcfS6!U&luVgRyDecc6vV-ORNd zr+$ofY}1MQIF?r?>k-TK-{|k37^*qLSiW2zhOwO_Frn;m zT2Vv|?Kq3u9&_zX@P~n=cA$upSAEx^U=4v1G>_(@am10yf+Zhs9@;?8iy(QcPsvP2wyw}f3PoyKMOr6Corz5&eUEY zJ)!zDfqY@rt1tQ*JV6i(!OCq$z*KxqSDJTy;(V3s{2v5UTB=J$v}<+EA!6RiJ4?4Q zqMY*9SWe=jmD{Xu9F?V(`JO2YAXNK)^E}>+V9yxnQjtCcX!M% z-<+8msz;mDw6L;r+K1|IiA5*3jEH}5a2ap1+I0*&EV(vw!uqrDe>aR78APH1QI zBNiIe@xz@7WxNF0;^zf@-j9MaVm?9N@Sd1=DcumJs4hA{c>qc3Emlf8OEPklF7X@9 z=oa_Au@Gf17<}V6^>jru_4*m>L<_N7;HQ=11{j>YNqV!)({iXt{qoajY_RXR&5E7i z^7|nKAzsp2rk=ejxGWRBkHhRZN)?o>uW5B|st-eh^dvT9jrYd8Km{SkZep_FrRX9x zL{?LQPzizEZ!l5hcxGttfZQ9xm2@ciGV8dt7c)v|Ry~LddWsM3E3Aq~HN#pf=;~wH z`5GVNvzV-%Ee^@?lZ!pI0o(6+Mox#lZ)=EoXu3jMMyD~>`D*n%O$fQk;0(J;&#%`? zof|PW{rmrxV7Kbd9NGK;erXX4a+K^JbDHyo%+J;kOMn#tC#5;oqzvB1hPs)8slu!{ zQdeq}wRfxfQgot4%;8#$)xtxGa`uAD?k>xz^|b{0ZHxx~U0JbSh)PTu(uRODYCc|6 zm1=Q+3v{mv()BE~GgOnVBk&DX146{-R~K0pb&`@7JR@Z|M^W_o-mk#x{*qWZ#bA){ zK>{1!Oa|Jd1aqGns(Omb4l`PchO;NdSSK9i+Zp=Dv6gu)7HZdW2>3^Jg80jHK%oVy zm!YM(rITKFHyr242U(1mrunSCsoqH?YV$TwLJ6MPjOB--0j{RQI7{b~IXQA_E`qA) z)STamsHGfLOBI(Aykvy&c)ve@9H zAEBzV41%Ey?aBZu)c+(+?pP111S@ND#n1QnswSGJ*9VgzZ)8Hs@0e=Ksy#gzL$T8~ zN=?xa^e3fh%Aw4=?M}`qA}fw~sVYJ;N|>x)#!tr;1GVeJbYV5fts0bq;?YCt z9E1j*fITVR#>~s*NIOq%HX8U{JoA(}xyIBss^!ID_%xgJl$Wh&<_=P{&-yjSp@&;V z+WE$*$1-Q61;W^b+=1n|s)u_h%TZYUIs9Kw&Uu9rh^RX)iL*h$NzNG8uJ*IdbB#_^>CAW?0m5hE*nh`aI3fAo)PoUU*2G1 zsc@Xr$^=DCws$NTX;EZKLVkV`cB&siI|7PqsUpXuu1WDFSTNuOP+4aE@c@H(GoF|x zfdW&f0DDXA|M9TDs8*t)Oj05taQk=vAAAhK0gmyIyfC=Z%h4mPp5B+G~+T(bCRa=+hC{mc?Cj zXdQ{HWDTWeA`;DwH)VGaDxSw3SNlr$X;VINl0YEc;o!PM_UbF)H9ZQ9_ZJ}$hn2Qx zr$e;}b4`_r&Kqk(efv1u&}s+8tmf1kbmuQIH&mqJ=?H2a3zd=~>N&Oxk^pO5ZL2C~ zERi`cSsW)@b~S)j$HjDhLi&He=d4Kn9_lF!VUpJlzF)}BDpdmMSXtDqw99!kTiDKyVI4^yJPu0)&j;krIbkWe33h z(5w0pkmI7MbrugdV$?=!V6cCHiqYsvb(KdfnsX!6x>oW{_{Sfhyo10uKYe%(7nWJF zw|$R76KeA8z0BrD#<>auR$*l?t(_)-pS)}ZX@~JQZ|p^(Q3)2x66KoJrqwqP9s}!= z>cea=sh$Q>7I$`&gTSih-L8`g=IWEgz0IL;y%j#Eqsq1Cj~lAMF@{KSy?L{!hQE;^{U^>&nIYp0A(L9 zYl3l)Q)rojm1Mb%xA;QJm+8U?w|6d`?UPz59QqJ=j~en&vA70U)6;r^^zfELdWVXv z-dw0rMhx+g>x&nB^W=+FVBm%pyR|uo?JUc@D^kZfW1>k$N&+MEam_u=YHE#h-n&M8 zd!nX(VpFOPo54^vTtj%1OY&Ab(OO^+j{zmP#O-5l(@r(n*J-MD5Y0pUpCgV@W zx5a}hra(^B4vgzZxf}3;0u+dF?khE5KJY+3$qzP)c{b`W zn5M3K@przEF`XMqHC6Ow*3Y9qJ$CiJu2YZtwrJx!JF7>(di|ZdcXo2uB8cj0CzkwD zdXo`tw<6KLv6K~Ur=4bB>m-W;)Nk5548r%bnPoW6Z<~Y@uWTJw0NZ3}04QpQdBNF= z+qS&?1HDQLAo9EELcBOF?r;Y>=IJOsC@9_~X&OluF>WE-S`KXX2%s8AOt%=WAX?WE z&if*wX=!%o4>_1~b8xrED9^OZaf>;$a~&*r`;PEyG7Lw0adqjr%tW2pQqw5ReOZ5= zm!r;U7Mu5b*fgp>&X&p=X2=?YP;vbiHZ&J__yol&-DMH&xkITFq!bq!XSc@A-P}VP zxNm)u4W7NeF$sp(>h`I20Q+rAz#;PG-bt?(m;gT>pN6&WqM4=EXpS&xo>RbkusdXy z2vqx#Q6GUf^Ne6M|Ia5)GCxAN8)I@$%D<%k2KJK)f8q9f8Qu;Ug$^gLfpjVDP%0p# z@<^z!aEZk+>Ep(W*&~m1?2x}XAwxW_e`12{3^mtq12i^v0!u6SOx69r#b;SFXiw}i zcHiWhsL7feS204ku~Hp2=u*AFimUE~*;xbkNxvZT+3xF;8F5)9L9if}4lZ?0K@V9o zybM%@Y}a2kiP-h;?-1|4PITOT-*(ntV#%*MA8T3+YL&%-=NLM9Ce|y`O`@x25$#UL znukPeso=TBbX2qay%2=!Mo#fI+^xcvHXi|kWz_XdXRg=he|H{t|D@se)e2Rs+q>MD zXeQu`P;gD1B)_;VNC$l*YQP^@dHF((Jz=hx#7a@^zp64tVcGb_4!G|E4J6q0de1v=3t3Hi2P!ikfT zBG==ppSNJfrO1CAwmby&Ra4;4xJ}lGz`$Pin<@2tIw^Ys-BF0|+8fPXk_8SRO;eVp zXSK3nya@?PbR}d-va3{K$ zWkBbQjo|c4eIrq?J-V1_@6iq9?|TR3V1f@U<#Yv$g{!xOJ*V!`>Kg8l4Uoe(!d6J7 z8udgO%=w*iV9SD=NEF|%gLLU)*Ui9q6f=zHcow&@HJDvjzTukhK20n&qT1;b7Iai0Y@!}PiZHf zgm@tNHS{r2#RWqp0sSe~AY2}4t#O&A(3#ktMotfJF8CK`ReeaW2(e}qQPG&iduB8& zO#So#RPdwN3%(r0sBdA$6f+7YeTbGq{^84lP>PzHkjE}$crOu>jwoD`xe_!~{Yi%3 zjW5NJvq*c>?C5=4?h;uLk~J-$-O;^)Dmp-_nyEKt$AbjOua`RkH;+N<4zQkkKKv6t z2o~2A2X# z&@lAbEPl9obVNGv8|Z5Y@SC-u?BgZRZ_6c2z#uN}-K9eiZ-~mMeF-ni(@=I%dM?hA zv1(qV>P*FY6>1DHys4xWXxu96iOsYCW02nX)6Imuu||yA)@BZqyN#dMGb>N8K9K6X z@B2=yYhPukNi+!BVg*fCpm_-tlnE=>dRo4*u6`%#6ugS}Z!IiH!ww7)&*B|#gbcS7 z4tnw%wW!XD$;z>9?ncPXuIlibJEh4g>EibTGQ$TevWWUYxxh~}cyV~eujM<*cOL&i z@X_Hrj}17iAPZQdnDkMYzog?JQ>Qy2rno~6>ky53JO7p(xe;jDRIRqz)s=yV2#Q_9^e9AxX`qXc^6N=zWzK_t_)|~X} z!9vlokG747N_1{0T<~tu5ll|f9RxD<)Pj0d%(>rDTL9^FN!>3$@cNmxJJ<|2t!4$q zHFl@em{!w}K;S)F5{Nn3T@Ph%RKHMaT7hwUGV`>5phZdy?YJ(kvCfBdv|v#@os}ePZ4e?d z<9@Lo95)PLnj>wi!;=S~z#6Qxh3PiEzxx&W{FM=OdI`0m*j5pK(>2QLB%YU={=-MR z5cQ?ff8dOw?+S?>eMM$mf`P3>%(uF`E-QX{dVBS~V1Hn4oHUZXS7LLB72}Q3bm-h| zb*lapdSkBfM5~tih@;w%c||>f07{Lv{S=ye5SrAY?fJp3$ez}}`&4)P*V;>k@Dnd4DVThB8ieH&G5VV{Xxm^S$v5ut`o2Jv;a-yxm(!r4 ztx8#|^jp5sYhH1#Vo5>fo?5ix7y)UvD`I0s#YMA7Mqnve7lZTd!j})AFV$BkQ2V_HE6ZuXYi`na!eojO zB8)EHgGY2sC+woe67UeD#%`s_r9;|Yg9)Bjbd7jiOBeiPED@|Kj?EmuxdL$zrM*xG z*%|bdxw_2Cn%~-W-wNgJrbo@tLe=HFr*4>+lvS+wvfKW2ZlMgVf&%2mzE$?A(~fRQ z3RVJw^r&a#*$q}Fw&nS}n9&`It#RIN*40hb-IXV0Nd}q6uZ3iX!H%|ypJq!vl2AWY z)9Anp$BRu)4TT#S1!(=Ldk6KYh_JoB^eiV#Yt`tlhBu4)3-RT_eYMFmQlD%WPzr+sm|-3=rx7X1A_Il`eMx5@_?&y zMP`()d!$I7pB@!taMO=xy#UJdbS>netCfYQvC^Hh?sER^nf9rbfnjz&R_tQ6>39Bw zH~Z2>?19WZzt~kuh*fxC%itd5Og?~ngONH4&~HSozfNjL{2u z`G)4GdEYj$JPF@R3p)DX)Sb|1fE78_YZM*E4@|%5)R3&L?at`&`BpKG(^hCvw)D&(pLT z&nw+y3s3>p96wmP$*+O5?xOGPqODBWV6%FB!pYyZ~9<2l^!SH?(xu4%macS17J8{lX3>E-YyQF{Bcxh`Aw|KVq)+M{xP_ zmA*m=F?b&7=1P(@O&lUzAf0t`6C=shPD6^a!YbThVkX;0@&z-Wci5k$<+)AX??i#; zK5lBoGOD%sFG-^&@albZ8)Q>Chdvh4OZlN)-?0Wxu3X#hs#Z)ELQO0#7X;r?&>scT zLPZN!NPzgt+PmKZpa)-$?ovkGE6BIJCex(A0@GbMC}w6At-&iSNaNS;hwG@%&Rf7^<(*9X5X<+{q%K!OQO$#6 zn{R@YDCk`rR$9$1zx4?rgjNO0xqSIBR`>pK$r11De2|L|TqA8<#hcWUjUC;Cwt%Jx zJj=z^t`e5wVZ?6h-73G=nT9QWd-@U9*GL)3Au~D*6!n%D=A{;9*GoZB_?U}_7b94b z5V56>Dd2C;8iVVt%*%w0z@}wGwHqK6e$^J;Lme7r>8Nfc+?Z&WTE(iAAMxh$ncH_2 zPa)_+dgi)-+tg1){YdLIdw)&iNfL>d4AS#7jYc=i2D{=derg4xAU`dT01sJBe3YJf zMtJ?eP#FM5liXpLxH%1WlvgPlsfCC1l}5_QhM0S%clsZ?ebMVmyAcX(e7B5xDmd6M zq!5!xioS8;l1ixsgk+xpnTWxvRq^=ef+%(T(}=0t=79PHlD{|kYiAml4!-YVzmpOj zyhuR_ARZ&4V*jLvKuWn6!wg88XrNnf7)Lc%+oLgC6@UWO7C|yY-yAQTZ-%dr!Pfo5 z!9akbFSQ~4A3Dr0aq;uIqd-_mw?w(0S4UKh_057WJ~|s#&qR%pz_S$mk_c_7ho;9Xz7NbD zVfuu$Yx*{VGAHP$Zz1Tvd)Ql8p=ZrOXguN3`jN3m3IwxrE~01WLSH6SWNg`SK}~6t zMdepXLZ7$L;t1Id0rhe4QdB3KN$>JxC=>?IAva9tX~|$x1_;U^cOF2u&9;#CqHk)Bq+|A9EizM zarY!Qh918aZ*ry;!zcRLY=7%*cT?JOCa`_jw|!2#bzsvHp#hItB zlP+SXo2OpXzqrq@kts^1r_T-2(eWpatNHg+wlE zK@Z$4GM?@R^g1iCxK9QNIHi%ohElbTmLkh=0#c*}@A&)xSkf=ty1`J+=b z_J;HJF5<@~z&cJ-sASO=8cN%!_jeOI4Y{CTROD~zA=4LJvUb-HS)9nvSUXDF{*~Sq z%bMjbSgjBs!zJADrp&%W?pAO)qBM9lT(=40Q zPs;-zFJW=XdY5m!+;bTBXZ9{HSK8eAJYPA&mNeCH&|O2($9RyDkNUY1Nds(gaWM`A z={}%(m7g-Uf|o_zY(c~Oc!~)GcDRLTX63S$d6#iZkmHM2eT*>eNa#~jRMd>k1AdQfG4L8;)V=mjpeW``Vjd~u4Xw9;O~xOX9P*qMJ05<&62IBM=EH;`_(#C~p#Lt_{Y!h7K6@(kYJtoP zk^0ycld2q=Ok=?0KCVd-&vzZw3dLH3Q@W4+WfzUUTXymrJ%WcbNAHVFVL!Kg>zYW!=L7oc4*UIU^3 z_H&8KG--WB*W5e}6i0~y9FH{~S34kY`zP3y4R8s!;U;b)zsagD!6%nMcQ)Z`yAlym zC+VqPx&PeF|E&AplN+{>_<$lVfO_b4BF>YsYO5)|z}FW8)Y_wcCbjp*X%8n?=Sx_t z5e}_Lr5>AjqRSKhO^v3Js^3bk>>P&TECILhv>!}Vq3uXX>~R$>gWbm9EC>GJ2|5Xz zEjOJDo*Hb3u~018u(0ewRY3lB%KVR2qiyRv=@Y3y70E|VUDof5;dN9+-rhY#%CR*b zMLH^I-PFX*jYM%dku0>PjWTn7kg&UGCEc@6~^1|8wSQN<| zVJd?AG_Tmg{&2T@KPtb!19IftXRT@_HuGWS6(IQIa%6O}f~@j0*-`RQgE;z5#`uR_JGzuuI7*(a~wIhr;gFVS&QbmiBCNy;2Xo5jc z%rNl=Ee$aIN&}MytvPDGxO`-VRSD`_ZzG%^a{%SxMSZUE@K*nAnEO{`_}n?&sE3X} zdh$IgAujJH16xv-aCl`*NVH%4@+#B|U=97HL?1QTF-D*U2|cA2@Tg9%*29m>Hfpex zpo9(BfTMn+8CEk?iIDM_JWD934IN)R=j6RTm1J>ycayfSD_>B9gLqGAgmzu&`=_j3 z_IOm|l+HnPbLw5cCxfQe$lNLR^F4)`V(&hNx(LPGKa3jCPH54M)IBp+qdp z0AO(_3>U3p)YWIMAXvNNoaPXzb#(#q87(?X44E~=5%))dn&BGV2`irh(?PSpw)}r+ zt4WhW>J3K~_(l+Kwf`J(at7*-xlGO*N`uOMj>i@ooy#^}myL#dh|4VAA#LZ_2WkXkSiQ2^39OFCs_PL@+q61D zA!q(=*d*+Q;_It_{ti!RkuW;JJ)Z?!3|;U8{Nsod7h;QD>QXZX_}OopoVi@j>$TK1hM?LIUDQp%D%X66|N#-Z1;=iKN#I(I?Ln3WgNbuEu ziEUfmZIqCI94|I#fBW#{V75ir6-0Xw&up1@R9NRw#JcUXDYz{XIa;wZdy79>w&!HJ zd!xO$naZOz7hp(ZjTrkoeoRpwhI?^yVpv z1M)~F?cf2)j23gef|}c8X?Mfi&GBE>L6O@(qm!)@N$;MXysmrTaf?5s9ZwIXsJj6c zZFOEh2y1hD+{VVjqJ0xlHj$JiA4)mXy5h6)daG?k&gA`qjjh%D0V(n4)ec(UmVWJ{ zY0JO;Q?@gW`?u6K6sr)T?;64oyP|7Uh2SzzHPqI1+~6D$pvj5H4Py;U&4He_Ywr{R z?%Y&eo0HJF&AoM+9bv9maZlFc?NDz1#7!da$^W{@!tAdmJD|U()f~T80>0g@EEPaU z(_9#c_d}_>E^EK@Wmw+ThMaM);i2g0&=(f-<^&faha^(-7XP-+0;Mn$Ambb~bu#I~ z?iCa_i|s(gR_Be7%VxJ>dcF(4WWVxCaz^*my$RZd?|J+PqnX@l>MKO&F1-Jp%=9vdo$F_-B{7gvuPR5s^S((6T6sD!QO#=9e@z$H+T93c5=cARxKaPM-{b$bsEiEk!&^)pm^~ zq#N^pY>BT$nh`#*4^u2FSkq8RFLu0f{j|z z<7fu&Ab4jmvQt(EU$%PGRZJXcbozEIK#rZQSFIB137!2UCuTobM|60%7Zj_L3dJI6 z=4!lpL=RkfiJ_owaC7+rG)M-2dZIPC*4llXoqs)PAJ-7#yK50aKqOjNZ6dEUHQ8D0 zexGlg9!`4>DuiUgjYh>@L0G%8P#{xiBbolT2`*W`@ub*S*A?4bNiD3a{v_@(VKsAGTJC(W(90C!p;@jibZDW%^ikgirU3N zLc0zzGS6G?!y1>lc(*0#>+4q{8+?r z&8`mR$W0t<%!d9<4w0$oJQ1kZxK)ky8te3Jr~%9=!a|+o10b6;9r2S@U?$S!I!bUa z+oiB)@GpK~ZZwS% z`<2MFf5z$W*3Q#d(|aq5`s!(E#(%mWS(wg&%;vf|j#W8M#QZasuc#%`NJw^}4h|-a zXZo^j?XeF>PeR=I^I$}YYoGiB{*~=!wF>C;ams|BRJWz2$NwVH5|v+%y+^2&I_m`Y zL^m#!(zq*>3FNZN>=+z3e+%r{vC^&hZaF}+qE(Twpt<^t)0O-|`$!DK>55K1^z4!D z<{0`r_dGt`qk;1JxWW%*=0*3n$OTQ=x%CG%6s*ad8=ZRd%UWY;Bvu@NS)YcPR*%yj z=YD>SZ9w^))h2Pm!$MY#V-n{+sQ|BQcAuXoCPfc(7$W`;dv6g?_p@vZCj<@d4grE& zaEIUocXxM!+s54?xLa^{cXubaySr}S+e!ZCynF9Evv2YScgCRU-M{XxTB}x9SA*cX zfDlv)1{F+WzLy$QLJ7n+b)AX7>*9|#B(hW3!f1{9l0 zpz4+3H;s&Z)QKXxio^x7^YInY+vjg5&u%D*`{J#K9Y3y?%z?O^Zgf|zcQ$oaltCun z7VXPGhzn96_Ts{#qJD6%l2e_>y1+*pVQh~t;1N|)_&A9={HZzf`+F5o*c&6CjtxY- zyq8|2)A-&zqI>Bdw5(}3_{^!@Ri)k%DdL05Xwr+yqT=$^0FJ)G*2Y>UzT<`Eg!BC` zZs|eCKOF9%jokzTvvZIDr5P>QIrRQpsa;QwxTX58S(v1I|-X!ny&zHAL z13|NA?uVw>Ds;+0`<+fb-OYT_!1|pC6feY4iO*}%k2)dy@gAgVuOjg(J3}8M&wreb ze1=jaw3)PZFQec6o*d^TdSx84Kkb#ENyVXHSNM5lZ zXP$tJ1DXA&{6%+x&Z_{tr-`}EP~zwHA3(9K>RdeKo5uN9mADmfgO1dF`fshOS8FhC zXICw01#~$~QVnd@ty$v{Y2R422b*hQzw_ykivi1zVn2KC-%h03)0=Az!kJ&=&MG+*OetL+;u$^>OYyy0UG)kJhF z6RxrgFyzF;Wb&=dQ@a7oi}1&38R?o(b5t+x*GJoe%%U>}9*G~vs7fu>YujgF3jZf4 z%7sqL7FoAzRb3>8u<6#MRh^U$wYFMx=|n`TTDCh1zOZF1j&xZyt+t?}OL%)IpXiifcsPLx&4>l|C1vk{ZEsWDS#MsS~f+s?7^ zLrJO?Tgwt8w)%tbs-$3SF_|V}?c|GOhnOAi&M}6wu$~>*8+>g%<(kS|2>753i};7k z4X4#+X_e0%J@>(mVXB}PhcmYxx%~UlJCGpPPJ=ij7c zq3!qmba%dvaD?AP(GG@NJn;2l2A(u3@nM_g#V!HInyg8oV*6b>NQ^ZD`NJ1up2w#8 zDixiOw~UhHw00r6wy9g@A=&}M4cCVa29FUl&+>2}&%23)hsZ)t4gvUAJF#`-+t&Q+ zr{eVj4x19^3i3=F9216mGVXs4lmA2wprfN*&c8-iqAwH1_IHHcej2 z$BrKrVj>_+cn}|2CbfxsunyyrARxQMC(VpMp~hAMT>-Gc;MTGG5W)r)84C|@S*{M4AT#O{OPz&_>r3Mh10B*w z*WbCTr2}D(qjqsr1!BCUuCY7EDC^nR??TSu+}d_5e$arXuzwHw-y*=j#H0mU7+|i* z?u2o{Ltm>-R)@@${*aACe$(^S<@9s==a+{__>RhsK+uH`w85zZ zjZ}lL-2M8X)pOh5ejR&f1<)X_(O=iVX|=kJc&!)mcBBwFCkoGH>a;f9sr-N?Ejr&O{n3$dLV~T7z*#THYYIBP{nt4E z1)jR`FfI!FNJC|$~@g=)MNx5t=0Jf;}lkXmiY9Vj)nQV9%BL#ELFYks5L8p%Q zr_1&>Lil8tBEwxjUi;%xnyZt%mbGhU2y|@EjNK1h;a2@|4ZxozlZYw|K8I7D?N1DL9cAv5^?}DqMjd46Ai-P|IWLvGg&8I6Bl1gxD_q5 zr5bF(!(R}9wiE%oc0cntPj|O|^c!P$>WBNgv66(mIqXnCwe-G7flr4E=|1>hn5j%f){R^l%a9FZ`fo6+OM==sfGX|2m+<&lAk7q* z&gIQJ#OHrt?ey#2K7j$*>1#enY>+Q|}+B^?{ezxKY8o_QSX5l2He| z<9d6{rRUg#4Li<$|Id%|Kz|WEBbXq`O``n`wIi7ae~_@!2ZrWuoXC03a)Uhak23vL zR~R%t6@Nqn=+P>*9>l+OcD{nDT3~qXN4JXkgw1^daU&eG%PO_s!H8O>YyaPBH6;SNz&}l?ylww332wqW z`<+0KBQXHxCs40+7oVfS{xAkhORyJbgr*W>1HMc{#6MqEA_|&~Th%Qy``;wVo@YHA zzbwK>>i2TQa!EFENe=xcKZTp{C*grg2mDA@jFBMc` zx>QdCLG1i)&;MR2XC+vW(O%+i(qr#m zHfAzTjsKpdpWuK;x^K!ME;iX0>}hCKHpGSGq(U75#k#be;`9j{$yw9{)m{ z(RM(ySzVE3aM(MdyH4YrmSZb1^^{A!nyYefi>$Mm;AB`|vh+`XX3DP<%$u>7pfx`> z?p|7t%uRjHJL4uHOf`R}wW2AZnCLo~MuKTF8Ma=I>876~txH43nC3(V2# zv-(|%mTA^>8J4H`5cJ*M3Ugsj6*rcI42r924*EoSZVd3$kIQmwWRRDue8EhrLV;TO zC_ZtUkxN-gF&*)WD@P9qiI=MhVg0IUN2A+xA1E%ku8YSA;8b2a02eZN;H(ULdlGV~# znY06WJCG4G!FA-Z&k}R5_}7Y^b@lO$QVW_wve^27CcV(7<3OM1G{+&zlFgQo%*O%d zqp5|@rHT_-Q}SF+Xk)U9V3B&bQH!K@wvsOsN#oR-YQn7Li44!Gminc7+ZdRS<=IDO z948Ay0Nl^4=YnUOR@z|Ucz@kmnm49em8l)6ifW|#FUqE@&^oDDDY~td9XDrf71dQn za-dt&_77vkr^%Q|5M zujh(g37)E3xmHp+f>|W}1wE>`Qv#Lgx+iZt zB-vLlh5-t-c4w3WjLr_Bb&N?i;vY#pf1)%s@+}d!+l%$6Pj%I3-5;3K!fuC$T7nz$bEgTEsud2gA(YNrgQ0<*ur;@$9FwB#VY5w|yWyLzGikD*3i@J2~30 z$-WhTx23nR-pN!qCIE`)$F3xqq<=d?rcWDePs95Cu+gP0d&0rX;_e*u1x4rl}>5-d66Bb@H7sb!4o==p^NWxuFu$Xy9Jvj@DdTN#d$I!~R=?W1xUZ>T= z^~rX#$LupEu0iGDm5niu`?`9-$>QXNmeJDiG!8(_wx~aq4!38OC$5 z##JGY^~_tBTlQ1Kz418BrIo_KR8k3pgJ3~p*kL_Be{X3OUWLIS>Tv#V5|jUi{O&iR z`qYsk;i9gl^rXT?Vp5~vkGKNLdDPn^4{6EjD8x}UL}FsAzZMrfX}ZGbb$H&FsC`^) zcMw-Qol`TT-jCE7Usc<3r=34+oYQhDF`y||97DO|jgj9`!nD&viR=cwCR&OieNrB7 z6~y<;rsI{IunEaw6i;HdC9Sle-7SNrTbp-!jgs_jFt0yM%(`$N_JOPYRX7BzPJ4E{ zg<66<>MwVrM!uNiq&t-sM-@s|0LZJqsGc2B&@!ywl;0O&Uk8)pXbfbJ<}s|#I=L6k zXdT3Jlr1b+6&_~S8uHGW+-mHVT1WJ1ikpK-}1EjdoryIVpAv(7gfut_W zdCPv2bcWxe3h1#7?a?^lutY+mxg3^I72?}gx`dpPQn!_L+CYaNu$|>)Df_N;^(Rw# zH09zn^!<1vpen?69jWH2bry`PQ!n%M79}T%j#%Pl3G7j4KN?bVmGR(?tQ9pLYULfV z?>L8q)d+GsxDRX{fc&on5^6%rbzv_U>yCJWR#F*PIuA?L;XTL_E44a}gY{&8r5q<( zuvJTBOk2L4I?Z2B6B1OrRD^Rb&~r&9OUE8X8q3Cl*HR{+LdVCPpvy8MPA8SSxV@m{d{MIGrqXNDdQIkyOjSF(yC;Gvmp7%0!C**ht3 z5$q?Y!d{>M@G`B&0i?q$HfR&Lpa%O$qgc z$9+LacHPluod|P!q7iL~zw07)3GQIt%KUKXMJJ5uwgvYGbDUtnGN{wUq!HT92XUaH*CK$uxuOON^?N*G{(Rcg|Pq zme@NMVcBuf-%Ryf(H>AenbS3pKx=R!FHW>yuDygZpi%a$HNMp1VUHwz%v;A=g8AOH)t-z{I1ix?Z!9AvhU9y<~TeXZ7WeC%B+#;z2X=2~{ zKLn4Cm@$asx{taU?_8VCRKaz$lb9M6$XkUL_;}o~TfUZDs>FopYb^y5c!L>&>N*?(YJF>>&;eXPiqV@Jvs5oR>ZBY2_BFa{%v=3;KR>C2)n`>YH2QADhXtH zVRch@3a|Q&9(zH>L}?()NZI$)8cifi>uQ7(N4UgQ?&wu%oUzU zBweu^XkVF_n0Vu zT2Iw)efl+CT7$iiSZY+^Wd*F9@_P~SK5+hYKbybz`DR*8j{|n`UL40bTmad7pF* z1@%bM+r*l?w}rZrNzZYunThfE&F~k&^FpCe-+=u7;2dNctcV(F$ky?aJ0re>Vn=|eG7Klv(n!!X2f!a<0J=Gq`rqNq63@;Q|DA_#6UPiIj@U(ud5 zsymERkRYKhDr&li%r#5l&9v>nW{2arSuzO1p!ut9`uF;yAdd%Qlr)0<#BoJdEuRcL zFE@3G)JSeJu#m6B(a^v>xhT+*O!*W%kf#$B$piuSxr$H$s#WN~rJp}A84_!XS(Xh@x}MbQ2173}__Tguz=C52{xzKXLd(Z>16!mvDVnO3lvC`Eq5zQGA4S z1(0-ox{IOrWM?bO_4Inx_mEq4k(ER74a_~|!hG{loN%*i(#le1rlHH&r_qe#KRV!Y zq*6d&ke5% z(FLk{e96bvA=DUxfAkF}$Uo?Y^DByPKyyfA^M&PPwO|B;+mKp6bsl>S=RcjQ3^vQ= za=Ykun9qJ|K5U>&MAXKFd5(9vq})5eJ!yH{WP_csy)!^)b+N3X zj5pr44@oHMrD*yc-EzNkwGNL1_yJ7e=O6d&v${Vdv9|QVJNb&3O|b0*U^uLB?v404 z$4BA?x&pfr^ON!RDQuX=7IrA)ABE_C!I`!q;Wk05F$m|>e1%<*{R%PkQRupC>KVFin0L2G?3BX&f$+yo z7%G3ZqdjW(nYUGScr4;Rj@(_TYH5ghAC>5m4a=`y!lO_ClDX32pIr(X0WFFXdmvO>J#;f=eCVXAzm`V$PtTm)>W}UbEF{E1U_q-ikl4U&f zPjtVKMmaScigWtgVNzGg)|IHzyFB&>1z>Z1QoiKXlFdA~F&Ex@Zuw zF!!4uB#xvKN*y%NAdXIDv3jgCnf$v~@W7O7v!HK$g=A_=c|M=*ha$Pax-^(vP~f9_qrUsM(yfn@XIyVkT?b{x;HCh;ay${Ddjv#Gl8p;%#`(m;Ep6{&X2wJ+u@FsT-c6Cl z*M9%>fScK)Gn#~P{*bj(hQyM$tePYupS zTwxqYuXU>HN4~=y&Rxd<)}%Jmuj3h*0sR>)q%$|t+ax)k!ki{SC(ZXK#E(;$tQ`J2 ze;HYlS6@SVq{65A5gsBedKb?)9!V05DB84~FfO=m zUIwR^`Vnc&9^_Yl5619oCyMnDTl+~*jT;Z5%!pgm_yfTEww*=KKWR$IIc6YyI4E=w z^@8L|vZ}&P3g;3a^_-7bObk`GG$1+Gzx<1`W^VS~1qtBeX%@@fDh4#XL72z227c)t zF8yf2A=h^x5$2_+<(|y8MOj88WpiC-#Mnt4F4*cR#`pfz-$VFH{KXb}BZj5ak1AH# z`(TP18O@NUI?Yfx%swJJG>miTN%w&Moe0EN`_t$xmF$&_gbsx_Qxxne2 zQ`zU8g7KHr9!Q`!_i2%$oYHXsD=H$CB>lm-C|6fLvp>NdkvgXPL6j?S*zmOk?4a;8 zXez4<>YAxkZ>5)TR$jI_tI2wxH9(txJmK%G2!2JSo4>DKEAcXFYuI&PajUT4iv*5eq>g))V}j0U#2w+4#6&P+MkYl&F62t8N+S_ZE~ndy#)vzg2iX84 zx|y1eOv0B>5oHzmRadt`=h?*mkzD0$t*a}828?5$)^uUn% z>~=T5^{-k0%}5V|1P-Tu%bz8&jY>66><5brgHz9lamBMo>JDL=C3OtekLjni@-%7` z!aO`akqjUan&^<@G|BF;c113VR-ea8eTg6wpB7DwYLJ`lV_&e~ldBArdO&~a=lxtw z`WKodS-~6E+|Nv##B1zoOYfDI^b(<#p^0SD6Qg0clNI%asZ}M~6^nPnE;XJLq(ar> z-$RHF2I@@OT!v=FTl1la1#J2vklTjnSqOr=Qs`C4He}ly=Q~z$9 zofnjqjY5o%>X?g?8&Rj}Xb_-G5+#o?y1m2iBb8?M>y%3?BEglBvMsOZQ>b|Ta67t3 zmI_0Y$7q-00hcUr_CVF&i%!}C$s~7sj~}gs01~X^!eWcihL1R_b>yov02440v;>oi zf&LQ*$74wFAA|ZSkX3qb)Kz}f_i(GN;<`s7MO?MUM%_l;2~(z-oRd}@R6`Y%D8OG& zs^gdS7~_gqy+%BsYwAjOA{ca6r1Z0;M#HUOJVEX0Fr8B(|4tGSJzv4f*LnSW#|h2? zkvo+0u}R|7R;pkO+Gg?V6h7W2HRKW&o27EnLwJTHn90V_o=SBB&Iirn(XEZIh+UE} z#W0~EDz|}PDm#a9TzfgTI326#H{1<~znv2Ax}!+SpB--l=~G-Oxas@DU-;uQCow}P ziSv|Esj-0E2RAL%IpD#ws$(A+_%|d$hCq8oGe^QcjDomaMWgTEi~vl59tyhLRDOKl zv-!EULTn#GIGBoXHZUQXF+5Px6-TIXJ#4Ab0_Q2<#t4A=26Ex-fD?dTrbCJ^;K8st zt1d5KI1qU}I^u4wpFITQ67P>s_|5u|OvmtV-4HVg^ls-_GWfK;14i1)9;;%yW&>6_ zne#Zl!Wa*HLn_|S7EK#Do>EE?7)r0^xIZVC&d*!e3R3fe^`FZdR2 zHC=p?Ek40}WDX?D31Yhm`y3B&o8@;ph!C38n6=~ZM4g6xwVtm%cg~C<|KrRUptC40 zyG7iUH!6dtd@yMYp2D!gaI4iB#P(I0)TT^I4+JFlkKHU`NZRA3Gz6tmQ42iT~bTVBXe|}7lS`qK2_pRhxtC)mn zPN`S@Bt**EKX@6ozu@4Ik_lC9!+c$!U=!Gn^;C%KRyTKt(UsArU7A&z^^a5c7t_m(umC7txOV%Z&7nMYjo5gF#c`RisVVMD5WO zx3{a)w+X?P-xT8XY0PBhFNHt?qd93k(1Ypz*c)DRFl9^W+sdftQ%IAQYqRBKy!kU! z5}r9H$~+FAM~J~t811x$;6jxFhIaTcz<4sQSLAs-OdoA@kGh$nKZ|^a2+YcKcG&en zPdJHD#UVE)nG?GOLXeoH%bWG8*@J>b>Pz^La^_$~?*&iN7gj)>D2F=5t040Bucq&Q zRm~nf6IL;h691l1Trf$@_GtUIAipKO_AMTXIk)rihSGa+!N4qn&p7$G6GZuG*L1_E z(;u(kufq`{)!lNqJ?F52cy&8Mhes#G18+N%e2t=a2l0$OB!Z00j1)b&f+r*@j*bq; z{R#zZbaNy#ZpMQ*lx>Wl7YMC+qRWJ95@C4@2<(iLwL1*zR;u>~uEK)K_g%l*lnF4M zc&GmQgp;n96PqA_Pa$MI_WgtL2}j3Wmv=1H1{#8KmaYhfv;d}MvaBet)_kYcwgG7O zUM6N{|Jl74g(P1X1yCutCS^xiUgUdRkaO)l2^%i;?7Kd8Ml;Z|>mxuQALZm%%0b}S z3n0q*7pt*i#2yr_*)6gKnZI`vXDA8SxN4fN(f4r}OQ*zFaA#+$w~q?9T%q(sw2ZEL zB|0+H%X^*NZxrY^>Gqs8mJ4Gj$7=NG^??%D3PI$sN@b(GP^FR`-d5~JGkgcZV zNy(}ah>snOEFqVUM(oHT+>WL~~HKGH9<&=;Vh<8e*o9*NSVMmOoTmKl@c%FPp0s zq>^#nS?|WvEk8F5PF`d^>Ts1)VJj^B8cdq$%7ve3N-k-TUo?4NQK?qkpuSlHIHNjD z<(ESW5lt6Vn4IiU67GyZ2{Erknf<>ONTn zwka{CRC@f|Hx$JD1`2wE8ngzu@T{BHtR+7wf=$6u@{qvzZo4!u%=k{6#xhCU_IZ`v zG6AKMT(7 z(Y|kQL%~N{Y%ji$dWvV!yX1wcce!)717weIAX<@T2k4MRA4El%zTMwD~3CMmn8x{{V7qCX!1h zQq#_N41Xh7D!GNAUFmFoLJM)7?B1$b5K{CmCf3Qc2f(1<;M-;DWjwL^vQ8Fesn4!$7BOVI6k+W&UdiaQTofc&V>#K`v9Xyi6$Bv~t*5bEpguf;uJ~J{ToyBP_$4BI0De+GCL1f6 z1dEAXHtCLp?VwI`zv1w3ALBXvMBEyaOmyU#&bi$wLL0dIF!IR5Vaov}<4vc?;$`2l z;lv!<(89$RkDvSIcoS8ZvY~IRL8{!!h|yTyYTH7RmTtx}QLJY$~Bty%UM{ z{nY-B>MJ4RLnk?x0pJsG#r)Fm2i1DaFC%Ktzpg3C5cZgXXrf>Dz zO<%@!AAdn64CAzof|6ZUptJ|oJO!k$PtdRD@R)ySKC0~y6~v}G+eDAKvmPlitwV5S z+SqUL7aTD;jz^Q@xgii*kHYYbo@!Chvp?2dm7?kpzjl zrzc3kh^Ae;z%#gywO2GAJvh>t8gezKxL!1q0k^Bk=jpf)=LFdVicg5>*Aldns@hK> zp%Bcs;G{4JG)`$q0TK#g^H)e;L}m^@e!s$i&<-y%?H>G?Gp;scxOSXL>4BN{8l1{Z z!gW($K-*$ysVm0m*8J0ANb@jdu2qED;l5P;GwhnJF;V)gBd%C|Sij#^&?TM^O% zlX*uPGJZBb0jO~)c$}@Ifbccay$ovs;tr`eE^$X{I$d3N@;#~Sk zv&N!;9KMRu_q_kC$0&3hz!z_;tn?lf3zW~q&}&?ml|9E9|3Kz=3AW#f*l)G(&`7@^ z%RgW7tOMLfmh2`Tdm_Z=5d{@Oya*xabDAZyJv)%fW2H?KI(9-fk*&wb_xUsIQc;eRb7FUmx6bp`in3X^ldy4LXg$t)y#(+Y>`QW77hdl)I9%sT=nzH*sswc$#Qhpk zhy)}6wmM!Vt##fMrfKsNctbj=ud_-j*|(S@ku zg5;=(prV0ekCSuvrW+cIwjd9e8H7dq)3NG%QRm@Ks}dv2XM!1PakY)ZV_L(dDQd$f z)Tjh(8BXPVy!JRJ+XH2fFvar>3r{kQwP*Axi|Vkq3#Jo4Oe(n~7TfIen`yH>zlklw zMbY(7z1?yX;b@$Cu*1|ZL=%gZxpd(4hS{GksQU_!>91VumFxd_RurFaL0?N}ls;l7 z8r)3?_LmF^XvByjT($-QR~Dy8ZP@qmuhIszW>KyaR&jSr_+ESgr+Z#|B~=_nz&e5* zbQw|jRaEI6p_(=Bh6vXXx9HeG$vI*AG}gM=pRU?PsU|MhBoX2)1=g$yeWULz@wpOV zEqL1$7K#&{afyD#xvNaHup7Fhs7x$a(7aVVEn%~|)3J!uz+mB=JJdxi%X$Vs_QPQt z(DHfkm(kLlH{Zo7ynB;U)dAPmlbEC0(^cwAIyt~B;xfAxlrX$ zR~j#}n~Xm^O!@o@6(!*x$cn`PTwKR!j4B^`dOeC&%NBvf$+=e&x^TA|z*ponZ51RA zoo6^yai1>iUq>jXyTM*aWPYb)(<^U&yw2~8${*(rZnezy{px1{L$pNYs-Adu#!fKA{s&KnyO}bLR^H-Ot73Q^-Ah)^|qn6&%W}0t~ zhGqprM0Dcq(QbP%6E=&5O`GkbNcZ4UOR~U4}+qwW+a`a`Iy#r7r@-gzuP(@pfoR zgO2P%V8dBLUBzRg<>LaIq2>1^90Nv4z0r0|O@^p<;)?^svTRn?jiU2y7arBm7HsJC zX8|=vmjm=uCaa@*Q^C<{3zR|V!Ecyvf(k~NSBi$hr!K$)%(QY}UF={6|JC=eb_KN< zkKgG|p0K$I_8OD%{w2nffm=#dpm=j)J{GH;ANRG#H|)S79_1BuRQcbJEw@vhH(sGE z)*kDbM*#HUVqzWq{pGBF0F&Xo;r zVntSw4&-y(ni>xR%4DT?yoB@oK}(pg?*foQG0+GtwFkeS)F#R2>xNtvq4AVK#7D-l znIhFhjTj(+|6)Du6*Ro$qN9PBnG8I=nv__hclWYOwEe2>E=(rG0LxRjph;Mu`R4LE z^~%2pEO`68%x6z~yEWNXDTCp4GdG<*(ai6M<0kEKCy`OY`m8tXkriQk$4{a)L2;Wl zXUa>r@Et?gfx;h=?wtAP^#Anw9tC)Px^EGP*-p$n zX;(tf#n_S#B?90O@;wlHK1p_kLh98vf(?qeS_=NAAMhPFa2$W>he#Hs_$=c`?2OhP zx$hKX#Uuvy7#K!GVxCH+!Z0B(-m|jAGH)@Pi zILA^%K^$xo0CNF7_ff_rG#Krn)a&=0V^}{ zPv&7R-(X_s2aQ`v*C~!q4(&CzaIvl07DHQ+EZ{!DxHW&#P?Q~G8o&zMRsM1SsGMyV z&Y*u3-^D4*s4&YUGaCj%>GY`WD;O}oZm?iV%1vSg@`FL;wagH(!mWX{$qmET9!YjW z=Cs=hpfc%PtF$ZA+t|p5flQToaOG3?@v)SU;UuMCKv+hiKLNg?Gd zjkcH20K+T7Yf(i$<&O68QB_5JX0y453)V6Z(NA=XM_!vDdk_c4V=sMq{GtUYd>Quq zm!S9c9mhNPW1iQ>@d5QHmQ|DF_YNyNMy0n2s7D?FW$r12EXGF`rSu+8?l;)ici%lt z4XnN8k610FFnrLTRjaEW$uQN9vL&oFWR$MQ;Eo`%uj0ca4%nLY!gv{i7vQSCjU-JKh_d)J} zyrNVNZ>eS1Sjye&4cp_-aHAN(a+SlfWU;MiXH8Wtkmt$dvU)jMr;h>x5+lndt8CHS z8O$K$p_9QsW>N;x$F%QfV4wncrQT%Dskey{jqmfEeEL%DgFF!0Qp!9&tQw{l`;B=$ z(lffypPe?a-cMiuw4V>hS|SL!a->V4Rfy6c;t{2d9d9>o@8i9&=c6oC@8&HTn#75( zKZ4+M*-+W`ieY47RilIzW-n>24gwZFMbrK{a90$2P7jy09EyAau?_3!(-yDYCCrt|#IvXx$6GLQ zrS1L_hG%h>??u({FFJ_lU}WP_8_BE!!X+EL!c#wLjc4M?)`nUShQYx<2YO=Q1Xkds zp4sTsO0pTMKKkT}RN{gRe!ij;%Bm~@@QPW&@6lWPp=>Q_{96yzZ=fa;BI%yB^e?(1 zs&}t!`2CIdA(TGz%y zCyD8l%e+sHhw9ua#vKRW7`M{~#P0Q5elF1j!>zSR-vI|?j3!jd6RSL)E({KGPRX(hAPQ^j@P|L}l#0WI8%g{XV z=A3s3w>)JVCyP>(hK3L%c{V)GkYe;QzEw z83zYU3&@1g!o}VD`$@-K6FxIOmc`4M$GM$@MA(HAk7`Phd@ZgP*M3C#9jgnGOwZ}} zgNjn*{WdB}HH%iLzg@6Y`vDx&pWU;L21a3AOQXk+5g?`Tvr^K`aSWC;cA7HYMCXt{ z7hqcV?F*}$V!rz#_o2t|WkCJqi^@=vb;ehWS7zXxs*){X^u9+;Wb=`$(r8(K^<3Xz&*52Nh|L)E)c z9iN|XZZ~dHtJGse-Ss+12^#go!%;j=g*4f5JvXNMa~kO!+TP7m2100NgVnp_D-!%m zgvAD{x-%Ff%(US*Fwi%}GYO=%Xmv`W{ba+T`*O!Zo617;_#ilr1UOrFXaH=_R}4Bj zzXn;ZwaMiXsV=i=)mDhpCUzn$#%VlbP18>_Ol~1yEk59isAnU@*17h>3D;JTLo>x3 za-#>}qmsfnO~##1K8g;ZBJinXI6N>J9;W$4czSOKrX!-8Z@__R1q$|&UD1-=&4HrcFl|F`# zVTSq%J(F2K4T!MF$D2GBqSkRAEYm&Lk8+=*9i}lTaTdtLH&uA$9~MQDEIS>~v8_&X z*sEtd9j5-o@H|0Td^!WY4PoJlpmfvnaF`aHdDr$1?zvBNQfXe_Cwjawa{~t0kM#K< zeK?%%$$IwbNrJFLku9y@Ujd+>w-J%ChQ|3}GTqLSexyZfWE1Ulbw4%fy!&?&_yb@M z*ZlA$RJsuUuT|FwnzTCA$hi`W9Q^7FG1pZ!o7i6&kIGGsV^VgjTE7Oz5MP8qs&~|q zEQFI-%0t7HoN9og}QwT** zSsO+6JB>!Ka`eOi>@)f0h5qUBbt%zDR9v>Pvm zrjbXd2Q{kP_?-X(u2(gSKNFLOlTJAL_=8ENb)?X8+O{Laabe65$Q$eZJr$%(#=#9KcNn+EO{C__M8v5A9(9G zWImAS!`8Bp)2qS*1jEVghW}pyn9M@Ic55l*P9xvMQ?~L@j(wvx%A3ypZx5@vq zu_-6+qLn*uKa#q7P%;^WmGQ{qw%$b*mlkN}uZ#V^Q3n?Vr{Bz6W9=y_UB~U8d_j3tCXa>sFlCv+Ln<4|JmACpJ!CYQRYsYPT|k+Z zU0!*NPd-xHEeDVJUN)GZ@)U1@ZvAOD-foV*{gdAj=;oN4>!!O4)E6J+9pNzV$9azM zpS+zaFCX?0IzoIVdL8F+f($>l!I#wR6v6*CFjn9+)6QQ5`}khdAGuC6O)e`=3NpQc zxIZ+d8xKK1OhaKb>!NDUF>;P;cpyj2#(ThZxC_>3I22;MNO;?8Me%8#-vxiA zLAX&sbYLn6Iv4puMD+ABnPpsz_h1wLVT1A9H&SQP=_pXHO|9m>dD(D=tDS=J%TESj z1~w|er~Kkm!bbHw&wXRH2j`0Da*np?sa4YJo4IvForeauCmZj;jJOm&x`OgJD9m?s!?Dl|E5s#E($GWl-lAW=i;|NDO1&-I!g!)Y=1G$TNo2h ze7HbuqMUcnIBYe791$Fxz>wP!Sb;>hI!r^4>p1iw9E`a4(n*-mqU^X%g54c8@DxqB zp+#iPc)qDyFCes~<74o_t8x7WsaBDSzX0C-A)M`0onGVKI^op)rp6hRNiQN2l z(WdD>pt2kI+(8aHgh|upsBmrk0*!l<%+i{J9?Zz#);`A(-Xr?)xa(6LJ!w0_EG(KM z*`W8NT)TJlxIGui??Wr#smaOT`1KQ*T%4-URv%GVY_YeYS&04=E28RFqi$Smsha>c z@E$BEAb+MHv$0r+wb3aIj;}RBIA-o^c53%H)G0;5sQ65UE58!KtU}j7_H`rSEZS=- z{}NWSbKA-5-h|QK3?cao+X`*!+7lupYp_5i<(&Z27GbMVou%h7rs34OPIu5}XZm#N z8tp5z#m8g|Ts}P=XWs*vh1tcPj!4)0f>`48LOA2e{Lh7kQ8uobcRp{c{l`=|M zL-Ic;JLK<8yV)GMoI=p_j?S*_*Y(4gf69QsCHFh+vG)tAA@$)Cwq3^Hdz^;p+LYHt zZ-7j|Q*%eGAm-7vb|kw5LCqE#hxs9luZl;MO*(#hwdtL_XXm|`l~{%0A>m3%htbVQ z`=;j$kwC>%fSi?>D9@^bVX^h3#8O(80W7IsJD%Df;NmQjvq-DL+=y1Khorm>()COZ)2Yh0+!!R zXS9Km_$JPIa;5+5NHFA1dTHZGh;YmpC&HGgCYUV(;Z2m<#){=oyfQmk@S<)PT+}dm zG_L4@xK}Bj>c@bJHg9Dm=0E_U5{-okCjm{(M!-&G?-hu&f{U>J!lrZCpL^0xM z`L57n@V#cs*w`Sv=6u!Kaau;_fki8&Ui-YItd3ewi3!yjr_M^tp>)?i;jY0b9rZpT znJ5j1G@ET?LJuZkZbdt8S8FzWOT=Y@58HyK->#Lqk#SR@gA=ugWNnEl5Er z1{+o$3p3>;;kkU($NbyT$^G8a+2sP| zL%-0(0~1O@)s>*-#G87`m11W5Bg-YT+r19LQ(S@#bcUAO;-oS+t~rcb#ED;vjTY(a zc6I9YCySlb4q23}^2CUYgeuTT_`3PbUjvp4b2eRBgb5lc{q!KT#pTEUKd!zqEUs*6 z7l+_U&@|8i65QQg5}e@f?iQ?ZcL?t85Zv9had(0TcZb_EnK|ct_iz8&Pwm~cs$N^f zg-O?pCbZkUtM6)z=Uf=rQ|M2>_b%PWLAuf7s>$}BZiEC=MJb<|3nj$(&WMEVEyrAF zK3$qQCl1G$3Nj$PPN16zb6fEAC>6TTvIlTYh4YNUTdM*=@o{3ic|Mra9tH&%w__Rc z1t{$2*Aj*DfJ%1bCqudFa4W6@+ema45$PGHw$7T%t50u&41ZTC)5y4vO5itcs?@^5 z65BM7^6hK}*C$nsXD@@;Iz9@QPP_qTw@v{s6jOr!9mj$MR*W_0EVO3s6Zo&=@S&ST z9bk7SNp#}Du=yGpHJoRthnEIyCz^#7)%67a8rf{eZg>iGQOtGh_XwQ5afzjFojw|= zCzGXWXW6Z;`N(%sEELBl`w44i%udtBDN>WAmd!10jcP0<7`uX=A7;U%D@1j&6$y*% zttvqEvIeYD&5-3|_eSH=6#DZ+)WO|59ijdczq{|B5$f~FSePQqrzgHJblhe8-fy_O@)MOBz3KCt80 z!R0KI`b{kN*{@b%d$P(aV9k_9|EN+|xIz;xZV-O!5avg}$s zwyR2|tj$g+IHh=?OjjOhj^q?(Q+=?7>jJxU80-0r`fivLtI=cWO?G!&`ad-t3iP&V z!EMk#_&M)B(b)RCN3_V=bhF%AB!8BpUu$l=GF7=oIlSHyzFkanTimxH$f2nmI0c_ zzQx18&BY9je=tZzQQzL6N5(2uK?V|Dxse;nZ)Mpya2>c^7qTb2&V@`VtRl=Va+mim z45sobiqtznYZcJX*oXTp2mTOSd&si8Bs=m$WJya`4)Ghr*m80&VmoBqrof=Ot3%@r z4#HPY{r)gnyp%Ki z5)q9g^#P8ic#81n;V7vTx8{0sI;ait{haI1TWm0sYTH`WfuH|j5b81@n_?Vmb{msj z|3GO-~%fvzlrxc~!lKRMH} zLAz1bl?rC%a$F zjgulG6b`005YNIG=}&)!Xp=u7?VE$+e1+>&E#?!hjoSuj#38nS6Pa97(hix5vXFYI ziZU~4xN>9?otfPpTw6BvIu)J%9Ojuk#G5RrF1h}^?gp`j=`O>v{o*9CIfejNBk0(n z=(nvhZ>S8r-fzJ_SVE=&H0mX;RzhmBcvG&$Y`U{^ZaE@{?7HurAhY4=#$$91;EwTa-Uc?ys4o3lhzw!9Nz50gdi9!k*fe9<&D{!@;Kkm3$^;8H{gI@ z{Dq2dQF;TF`W(GO!Q*LD?P2Usl^YjV@u;@8;WhzF*EakyYYsATW69G8l;{hM$>-Y! zMi2PnBN-K|5U@Q9!+xb@Tq-7eQf=7;cyWhmaL9f`eAMW^!s?j>{#&>T84a2+byMUM zlT0=~*hlOEU&i^r$u|<*BM2=ep=d(OHogjR-(|x$2#RX#j{gj)3Ug0E8S$KZ??rA` z&>y(d%cN|yAGB3_ia-MFfwIR@%FZ24=m2|->(dz({+QN8A}fL_h{Itk7Bsto@|5W& zrcz@nEv6NFy5~(gLn;@RaRieCQ;Lh6<4kW9zd7MPPKX#)qL4k`aaNSR`LukS_P-|* z&u^rCt*=Z>IZonASY1W4pOs@O150=|cfb_GWir1|kw2cQ66eBVnK54{Wcl$wnIDv} zF^EAfAgQm=q;F|=4?B8Z8-`dvnKH5i`wiT)!syg?6X_4Rng#CXSG9BIPv6*-W+ZTyM$K&>fysnU%3 za~zjZ@c+ ze;+7_n1^l~-GlqbPO9o}e9Sr5&vH?F0oTii;1s`@W}bj19_)=QPOuYGQ>uQ8 zB&{vC-8-zuNV`c+?2on8G0~5P8ya8j->61c{bu^MfXMDCFT$L1Ew9<7^B~RI-t_Gu z+V)i%(|^iT*+~GvnhgYfl1q7VvB0`FTROscF;7Q*F>gC+eNs>U-e1ME=QKWnmS(hl z3b}zEN9%@b@h))0aDS{WLVGl&5kJ5yZo+Rlig7V6aH5CLG#_iO=|7}0D)bYb&o$@8 z8yxGh9miPMT^1<-_(E>euF0(pF8&49&`VDd`cJs78DeUwF zxK)U8gflBqGxs@pdUsHquK63#dPQ^9{^9N3@Ve(#x&866O1vCx;4lMLG1c}r~2m7}5_{mWcGEjNKtxB#JgMUFY& zHjGwhRV&MKSq7rFJ66f``J)zni^PI^E;eZhc&{8%X*wf8&Sp7EIx2cUjpYzGPl+X= zpiC1T4{XY_oC!Z#AT7NP{js{DR85PSzP>$<=fOz?jUkU*@iFuk~yq3 zO0$}2({dlYhqlZq{=nc>Y}Ceb+S$g|&&3CIyS8mp$8qoQeJk|^mxI@Xwuhr+ZbOI| zqkrIT6Bn6V)d>g&P13>gH)_qjo}njSmN*#QP><(hq?dSYBGM(G(LY9qTPk{-RA^Pf z-4fey+2)Pl(9CFn@LHt%;hTN3E?mvfHU|HZ(F^H{T!mtkoi=~pf}g)5gfq}M-Yl-T zTM5A!w@TogxbzNJ zQ}=ByJetHH#ZpdV4GaQ2PFfE!RYE}+jM;sr)~8hmx2{jQ58(-qI@j`x?w8LrmQwto zknq|*nyk1es%uSLc|D*MO{-%6w!DbajUG~j#AxAMskM~N-MYsFO!94Yjp3Xtx#V7W zF&XzsurvT5#p|b!3_M!vKEF&vhVfovu@QfLYwCUz*72SC@$d2f6HJFI z97$mxj&#}F;QIuWg{>v+Id`r3i1kK~gNkXwSg185>{M2lK?XJpnoO7W*)z#@AF6TO zxw;i|S8Py<%~jMzip7w`ozTSCz<#;xq_yk5m|ES2Z5yrm&(T9IiajFdb_i5WO=;mq zoErh%Ne<{|tn6x>ZST{uFGWduG^nn%5iP=Ouh#&pwoUAtRRuu%*zzs-DNx~p!Zpn5 zWQ}`&kuk$k7Sv1^AZ?EuSUvQaEd1GAAXvZH4ZUwps8E$JOhH|xKC#~$tGEWbM-N%| zv{AQTjD62RYv&j%MU-&#aYa2?a*_m9n zJMJA8U$QH3)2C(lGz&5#gj6P-Ih&83X7cUh*gu8R+=U@M(5YTwjUD|XUzZ|-(@&3N zu8F6+O88|N>rYDavQ#-YZvkq%G03?87GlxB8%(@>!HFoISgnV$C&s5rGaiH}NNX~< z@11Gyt@9x4&zDQK6hbYtD<&OI@3E9Qq{I8le6ipsOIi`~dy+7VevIHH85@)KHlT^i zlPibd?ZA)4St~i0fF|g#;6Pga8$4TdK`M5QaxQB4T39g)tQ|pDAz7*2CJ3ZYZLxG= zF`o6utA+<>`(BZp4X$>-sG`+aE%*9Os6cHMqwP{4n#b>v_Cb+!LbU2yHn%*c%kYg;tJ|BL`UjGHad1k8rM+Z{Vo8T-~v-m+)}YWjPJ2 zXdXAHjUXIor|QzeM&(i4ru9L`fi`}#mtVN$GPsb=c{Jr`g9B|`F?m@We)HGqa+MyD zRJ$d4rsL3t!QESs+70Lsm-dDhGh)D_$3^P6r$I}nSq8`lDua;c3?(Vgp*8uh+zsaZ zcZ+?&mjb8}cNS?b?*jvjnDa53e|W>w{4e=+-0x=NifW?OST#tBZ!l~f0|U57eqj{< zhKyC19khbT#y>Y+-WAW9HtjdW&SsW*VLUe)^|f%hP<(dvvG6!qQ||RmJ|eKYw?8Ow+gB9nbpAM$#5-`Ts{sy z2s2X)TFz1oI;U*vjq+TW;ZsC7Nvq~OrqY<Isa#V!`VGHH%vgf)wkw2sr64WcY0i7xE7KmQXW;ba1v~5$6uqXJgi-bn! z1N80!yHUU#DGrn?j!PhTTx7m|#y13onT|;`ufgr{$m6-^Y^_F#mXA-)MtdFF^ad>m zp(m9X=P$r@T0)hVFI-pnY}1*JWuw!3V}A|)=KE=UqBw20w}yv;mqtN+QVXDf>weGU z*yQ8rW8iZ$&HYg-M=!0iLZlj&OouQ`Lcx9d#Wr6Eq58T=UTxo4qFC1$DtTK*6N zf=E?RTI*IT#vqr6#jJ|;LmB8Y@s+BF)Q{^uUozpHAop3w6k8OXha(0|%ja3Y2*Kq_ zgNTFex8DRtf7uLy(-;8Z7*%f(cl3d%;kBPV#oNjkTYD)q-2Zkh{MRIl1e5!) zWIaRLsPBh18(mK^$kDkFhKIvS)v5pPGv*FM{jdDf(cKp(p4@`ho7oMzi!$341g93Q z2J2R9tPFo(-wQ>kgsEb>_4)&yqRbgsKl%Gpm;J^zae-YS!YF^D9|y%fr_i z7pQ=t&he^Y+13>IQ?E5L_YUXMQ8x(f(gjSXPoF+kW53jXg)9$m`9}Kl4*>K|jK>7i zvb4IltM%##r`qHSk+tk1($l5i5l&U7VWfKj=mU`3P-mgZUpj?-kjsR~#{W4-Vo{*C z%OQwk{{ayj8jHKg+9dO?Wt$3u$+jB?pU#=!oEKFAm(zqjAdXKr$<^$+$!K;oKX@;O3$&QbkW;0 zZoC#aY2@ZU2N=Isa6Ndxo;P08B-H->GSTSre_oPS&TuO@leBj$!lgvhAplK2S4Z~^kNUPS$GBCH0AEePar*@-#~)3P zXy-6TgBrHsCOis@qEwk}ICp6BMi)K}^HoB&X^W>LViOz2}W#wa(g(sEX(6#ck#Z_G=Fp*-TcCd*Ph4ui@oZJNFq+PcV{EZVkM&sgSI zQ*;*FIJ}9i0xc+dP(Nf6WC@uAP0{#?5dG0lmbpC-C!~_SpXtBtJyGs9;nj z64S9cM$3_oF@8C;Xer&B9nIhqU%Nhvg1p!$3Ol(x@h#|w$hbq^)qB&2N^%f>|!-0{aJADzrIT(|$adqzN3hPSY2Bt!jIuyo`h*M(I&fa(gtEJhqq6oB})b``EOPfk)!OW05jlv_c8w(XmCU z7^YluzG~=?Y_KFo6ccwwIF_TJt&3l?#%y^aAybntbjV_V1T%Ee4}&3#NtvmolRY0Mh8sDgdv0e78SXh_Cmo zdHql}f|`G&?>RNs%A07p%1XmYPld8Zm~v3_?gH=O2Ek&zB&GZ(-*=#Wi?CmVV(mkd z=9vH>m+aRCf2WG%mG?^w*v2THUGr5NO!XY1&52|20k3<` zI}@i*$pjbW&Ldq98yiYGkMB9J#Jy-G*SxBvX7^UJCJ}G(pR=7bb~YvH6RWcI#+<>a zpB;1_mvL}G0YI|8`wPjm$iR7<&uBqE;cMDXRT%|TP2&;YJBkgdp4HHcnJ5uSxf-F# z{j;5X63ZG=iHH?5SetNaE4$uzKM|B5PB+a;J=pJbLcLB_NsCEs!)QCJTE>%(olNns5Msi^lzU?Tz?HX$08P-)7|;;(%+d$Hp#_s}_^eSe$jC2teuR?vD( zT@eR3Y595PYa?;1==yuIgY?wnW#^(v1*D+QuClUq$Ob{}h@H(-9LA#X$uxf9xZ%sVi{iiPvYo~?}L45)!&%Q5ih zBErvygg60@**$EZ1fryq2U#n5w7JD^t^f%T9>%Kboa2l zanH1kqj~Z(!Odu6f0khLIy1*_+<~6b^;#MVf?Q4{6Hj~k$c|^AXRKPgblRJm)qbT@ zz42|D{SrSawjB6$n}hh$a$ACZGvMflq%y@~W$98I64G^^mBY~j+NdyL5HDkW$#U8U z6#mChPY2guF?#*M+C$^i$*Fs*g86=|Fw=B?}5(E#^> za<%78_w1Wq#3E z*2gd92)-rLkBi=GyeC!$&uzp5GoO)t(|za;dq0w#ySu1;O90 zKM7=KGuuw{R&UC-Q{+A#q2aKy=^=ymPhXZIJ_nwNI$YJQjHt2bxA*G!U*UH7;n*Sv z#n3)f_QkxdCf=?zfWN4~<@XPJ$l>Jvch*Jo;rh(hzBt^WLuZNCYSJ`!fo#ZQu>Xcr z7hI1R=FeNxPmauLFJaN&NrtFyu-;ELHymbJqk|e#A>A2OlB=8xYnnrYP6xYN$+?ww8~uV^?YX6A5;+< zsq**4Fq{c(hFsA`A`=eHzF#2aac=Y`p5t>CJX-wd8u$E{^~D>3jm(AFcSr|p6b~i3>;0XJm)hTcDDy=ZFdRFQh)G+Q}{2Y4mEH6bGbRtSsl?88k}%S zhO16gTLsS5F+s_PgoVB-Rnt4JpVc+e;4%p)ET1@S4)rkdd&0Xwuz%nC;Sn#p$vur- zCn7VW$oxIfrYBtW#eA0sEKd%W?*R8eu^JSm)4H<_C)(V_dD6uS;}Cr|A@m^1G*Vc9 zfP0-dQ>N7X>L;B#t8o$&(K1&|**l|o%V4vskAmRy?#r#f@mmYRxw_yMpP{LuSnUs# zz_%({X3H{{I;zV;(SN|*Xe3BlN2Q!RX_-!Ul9DxBNl6_A4dYuH`Of2q&0}KPnoLlu zRy;v6%|g|jHVcTiaXl&S3+G4pmV=d)^ipFk-IHu{ zTQ}5R6b7yK{Uue(I$BK^0bcTh0lpjF&mPV4*ySeq)(Ebk2~BfeY$%&kHFAVf)Ri;p zDFJVf;ACy?6j@QfOW&#@TXj*>g6hP7d>0{@y6xTeR$<({rI*BSsN*WZjS;^ZM%{KHtKw~{iOX|g~@-}t=0)VA$0D`Tw6G7notBR8Lh`Vp!tv8-^^32WZH>@Cjwt0z;6708ET8tlo?cB;&DNu$qp0T$i$?eJP zdR{JVyc3uycU;zY!O+`wVhY{iH8Rl|*kCI;_YU{cRJNbND5Gb%Cci2?0Dggd8n47>9`yB zplNJ`-6S=DEjNc`s=$k-e!pZZVtf9@eDv>5#&j z>qHwvZw%4!_8rqpel2pz=ek8n?aSrr9UZSv2JQ$&Y8amA(hGwOeahRcH-6Y@jnqh% zjvonNdt4qRey6SRv^v+WugL`H)BG|o;m4jg>1+HzksIQ+hQwfCMIabiXE zb7d0)b1h2t;BpN_w6J*dvM+=0rc3%rgI9;!#~9q3#-?$sAb?Tlpw3m4IEbAY2Vj2wDC?=!Y-kLA2U}N!{u2FfU*YPPt zJQbuBgu&Kf#}61vc1O6%_@_$Lh6jKY+@Vf>Bue=sf2gY$gyZhbfh#3#u5fFU6y%@K zx~8`5>W9%}-9DD&e`y5mMV1=w&Zt$p+z3vw8_+#36x#&23D|^vAOw(+KLyaA!>hJ` z>a#EWKCpt$=A^}uliof;|p=$YB}_1S=E(j z=~UdC=Axs{7n7YuwIi-9p%H?xaaJ!s_Vm zjI1htpv~Q>?v-Qc7_`mvWNBHyyX*HeK}hU!uppx8qV0)iO;ON~C1puQEo1=iCfA+6 znZAOb)&tgC8B#7V;c#^C6#!L!`m>l^={CCrS}ols@oomtB>=dhT!e?Y2&)ap1my;A zCgvVZy>8pJ>`x20w@ol!S{{N#EIj{aKKt`|BS7O&&?J-z2qp%642`pkz@k-@5);2G1j%pvc&GyFTTWnA zu!rF3LDe*^`^7Y8zN*{t0a5 zZn2d-#yP>)NwA`Y@n*w&kpY$H@>f3eIR)X7Ex6a%Z4gPj2GB06aPQvBrG3zii@n@|R_FSX1&ZArP{k_sv# zo$H6Qllz)Hi!~|i`Osm)6{lk`Q*~+P@50Q=Z@V2&mL=qZ>AsW@mYW&P_1orNqyD#J zKPO1U!k}Nz#xp>14E5JNYZ$*v)s@y&98J)+TJ(9e3t?Fgj-D0o9Ge~x9m7u=2& zzHU06XefTD!C&%%{~!e0LY+U|u2{9S!d5IK`Jx;-?1OfER2K#+ZWDq!HMXl>4zrwU zc6CQk*pw{0YAhbsC@>U!c&wdt(nR^>yHoh_F1>)|(=PX_XE?~{a? z5z8bq0gpWoa+}%G+#Er|r)J_c0>Z$hGs|S!L@$7FeLy0gcmYl3d~b^Z~y^TCESF2!YH<- z8%u;8C-YwR+jJp^k|>Lj2_fZqJVyHC8j}lM&*z(bYU(J>ONAFSw)wG2s$2!vxKB`y>30peH;9fjY z^baL?R(NQKmK9klLG!(6bRMSMnel{B7TE|Aj$L)`S-cblSOA~Wd5!Y$WH8}0jPLM)%}AjaqgBE#0( z0q3EmL=QQ!hGSxzb3na$8yC~^M-sBs9c(t%OeR6ZJitpRam&985R8w!hm)Mzh=+8L zLH+mT4^gP*7H-KE@Qojb2(+V>{BI^mCp88Z ziQOVI@;fZX?*|n(wb9TTJ#o84!b*k2=jwNM!AkD`e$+*;AtOq_AZ6H zUk7^VR+C&e;(_PgZ$#q;A@t@ahs;Mf^NeDZIw=TyiON^m^-J#e!e9~4RkQ5ilM1gi zo7XQu87?hdu&ETq!U87m2b1S;#(go^)3ih$Y>|4c^*b|OTEAO9nq~f1Ui`I6>F?UE zZSAH81O;^Bv5p=(NA^8f50x%yPV}wJc{pU= z*VCNQ0S|c{VlJCXe6l?ko84naQ=6jSFDxwZ+Hx}itmLuS&I$ub*0ZAm&t2TkqPwn> z4nISs+7Gx^cf#3F<;mRrwp&mYH3jDUj$bC1%}?-*zXOl{-~`9B9@-kADKZj?CX{4B zmHSmZ@>RYL3nvC69Z#20QndRH2v5ddQVj_Q#8_H||D`zmZ;qwNM@)DP%9&a`Xz=SK z{AjkeAPOIYcc5>;rM77ml7px1hEIpH&J~~9WunU9pml5aqr7S5-i>_m=5UI~!wMeD zif`Q=Mkb2$BFVcI^t!K^0Omj)ZY?5K^*fsty7$dasz(CZFR4yzluUJQ+TewW;2&1# zs1(Qbplyq4VIW0WcLZqP@x!&RS~?QoglPcHJzI#7=i)xgFX3N~WQtg>;?@@3qMdFv zCQ+dBdVTKFZ8gfX>>dYzMUkIu=mnmeWh)=pc@U$RH?G%}y{|g&1S1|Vw}$xBO_X6| zaxfrpSvG-TpfY8Px#R4Fa!2qOA~B}5o8=e6%Wamh;pWE80gj{``VL z9G@sp`8u7S;4yY5_A{ME(k7*0Yvp1EPt#n@RsI4DwL0#@x&HztBd`6Svs3KEA$20IcmdID$v$^$lUE;q5d`E(L$UA3i81x0x ztzU8bGy1+KLwHo*1^lH|GSi2Pey-Kd;Cjmp>p2mUT(opM4Py*g2dFvI7*DqPFr92H z5S79e7w_myQ&c@DXUOcVPL(|uF?<~>s$sfXt%fvXa*I<@6vp_=Jb!1=8@3EHAY%%B zj6s*&Eo0zw^iJ#a#Q0zS0SMUl{A_adcT0kZ1~jAD~*~&+srCh|!Y)9Egb8lQP8qLfqKNK@;azINHU2i9H>j3Db~4 zc(?E&;U1@`)@cNy3VApj-}r+Oe}q$Y^)D|9Ewd%ABeuiKM;qrdStg}B4ZszXKf4c4 zR~$V2hz}sx)rb+hO31775xfz943Ypkwz8T@@#k{!`__1MVmvHN*p{XbzX;nrw}v0B zNVcRi7Yvukw#gUp-^g*SOzoS5$aCKXHO@@hl@&+=LtB5vR>YeP5NTdnK8<+AyriDe zZ^7Gu5=LG7IE_XlmyyzUj=UEZk`*_+?|e?wQ6h=QLud#IGI;*ZBV{||OkyO8Fe zdraE0Un7tp=rZx&xB+Dg2bZnRX{z}xXN@AqAMlJt5V+mVxQ&#B>|3%}X23BvS_;EE zT0>D<#0NTIJr(2*HvFJ&+dx^}Z(tfr=2J12N)CC0Pe2qW-9ki$avg{_&X(0uCoGl~ zZnvR2AUgXN;QIrx~<|1e@K}07Pvh>V5@$bpLwr5G)(^*WA)Iu zQcn+(t$)3|H9EH7qkC@P*-(suw{c4tZMoB=DBzV62tHu#=uOmjZjHX7!RIO^x+mnpGjueK-xOp|fCX0^+Y zy^CiL)=R#PpOvP-d>6rs%=1@8{u4nfe*Oj*dg~lvGUIZ_;7HL7lKQtoDIqBy!)ELR zXO<|jdFTGn1PnmOkCG+x-}xcAJzuhiSc~RgcEuM&26sz!%!J>>3W69BA-Vm+I@yUY z8sw%Mxssif)3lqWd%_Sg5=GmGTR1k%^xmTBW#g8l-mkDEt0p1P^P9=SWauJX^f5 zo6AzjcF);522uM)Q0+WhMRGicPtK@6q||V>aD8xZ;ppLJ+-N+a<44yc+rER-_w(5` zt(t<7I6vIs@Bso6=1lw6QI6{`4?=qUM=xvT)#nvIXWr7NDK1p1U8Y10UC6d;7i3oc zNJD{uLv~9yi>FEG2CTHfpKU>KPemVac*Ocq2`fh)j{%Qe7Q_EZno+~t_@KRmLbnB( z4q7sjP95J?ID9ay90Q>77|yd3ss~-h1(1vcXk5*%!V1GOm7AnELg=Y(HA%ysF((FL z?n)H*k7vn@wG~)JaD-?q+n-87yUo zF6wHn@z>B`)$xG(hh=;tkR@rgZ`3N?rSF79Q}gA53O)eY9FOHf-|#iO0F-1?tW(Cc z1xl9tfI_V-VvNG1w@yVuSc-+he5}`Q-P)?QqXl26@6Znyu{^?tOBl<`-ywVAdXqUa z)u8jyA;UvLp3`ZTB>ADFa0KT0Nmk=jAu@JxoKxw$_VxQvv8UXJQ2Uj4P7_8Iv1S8{ z72YT>8;hux%X^MJfu<2XSPMl7ot((sNSThjSY^7C$q|8^pZhS1t|&16-6-DpR0pb{ zv|EX|kd^KmG>#+O|3rqmhynT%+%xrmAv?TkVhy6JMm(aQXBX0|K`5^}b&Klply^)aFd1`B&DVH!%W!m8oE2Gq=6i*gJ2q&7PfR_jA)&~R< z!u0f3R%8Ghk!pyAf7tyieOH2gKLV@X%zh+1mmIEZ%CR2q^cAx~lHkuhc@fJ10 z!=M+>Yh+gu;z?q*pfC&N00JoDsXo}_EYiyE9_s;xVE-GBQapKohP?jm*s< zZO5}(JztyriO-ZGfT8&@lZLILjr{P&K^}?~{sX8-ERv}zS`goFs^4ETt`7};Oe}*# zu0q8cv04^?SjgG5chPc^Z=Wl#^?MJW)xwOYGcwvry}EM8%loM|-TG0_r7go?XH)B zx5vp&w~%X7>N^}x77vdgSJ5YWo*kwe&g~hx1fq?_eYVT-ACG$9|LVfBwgl3nd`Zt% zA^r&G?~SQ?i=^8SIB(JddSq?=+0eRnNwo%=4Qw-Ha`|p0-&p_yFR!`&0ngc!4*GiB z`I279j=Fn0yCub{Mnz@Rw*T~VGVrn!cRpVMk9}Smij9C??$^eIii5)n0*$AN5et!5 zg!fd_T9fx-gK;9-_h0=ASe|IZlD$%>=68(dd=71J7veYg;F<6wf zn(aUOQT-^8h3ze>(Eqa%~I~a71 zRdP_lUB8teKh1G-0oa1ne>pFDdA5zNq_UZ?q^`d`8~T8g<`TJl)v{z;JbvGtRYd=H zol%i2gAW^C;rqi2v}i}%2D()DQm&b>%XXK#MMJm@ZMm~)>jQJ#Zzgp7m-^uBrNLj%laEN>} z&pS_q%C=}4+x1=J=*wH`J)Q607tFWzMDTDtE~13w=byzZ4Yvcs)y@-p1Kado`MesI zs9dDf?KDIZj3t+yBrh8+=7a>0kbK~UJ7Xzj_3wVu7Fg4K5ij%MN{y?s9Q|B&EWK4@ z77}A($ufn-j{OF2;C}rbHNb3pX(qhdn%!9%t+=*xsxJs@{6i})c=FJY=3 z-)k6jT~dlzO5Za4veM8e$oPB1OCif)wRjKR)2nxR0Q5OD^u*wr>S_P1<}99qCqLFG zD3O3`;UNU$jDsnGV06fyXxoUtpVkcZk`Lpyr>O|(ug}82Q(x5ELbeyjsb)+!hB!1AaK$CQpndAn?kL`wNlSya-X$dTSLGR+KL->+8>P2K_T#cJ z931f_9}PLs_JU%dKC$dswbF#jr*0_VOYXark_0lCr82dY0j(Mw7)U*7@L)1bn_Acg zF17NZ>U*p$J(Gr}cAthW{YS+n4S<;$qVrY?_6&_D4SVQgzgH`} zFz{OJ!crer;J6->Pw~#2ZK{v%oTCUWo9B^ntyU+q2QYovD|UqsXcr;eu-f2tfXm$@}>l)Q~< zVf8ozCvg8N#U3LaX}Yu1yA_WF{3;zdCAd^0i)M6~xxh=Mb}KlsD1LNyr%NRApyOjc z^G3;Bkyl$nGOaN-1A2kAksuFSIrnxDF4T#PI6;^AZB-upTh(s{F&~-Vct)~vNhL_d z97Kc!@Hvqzhb`K!Jc*^NX)Lk8avF0LkHdwbx&%~Mrhd5qEvWM4o{cwZO#s5N`#@8e z5BLhCVnkfh)%Z9cTJTzMWEZZi7YK%dgazNp0=^vG#Zy==Xuh_fbOvdGj?GgG2@P++ z#{IpFa=#{j$2Vy_3Y z-Od)i3&*`dA+tX*+Dk&^Ubd`S)xDIiM^Xb=Tcx0IWy(`?!03v+q0RX^ z*AZn>b}+Ilk^PgIOHu9HUq$>%Xf3V$cM&U*%Cy0<4@;j)jfs-ewmBSrNXeyuQIL(4 z6;e0IQ`M3V0z7*vo*nzcLqP6UI)PVK4BPwU{x$VH)vCH>#p_6gW>m=$;q$SzSL-u+ zD=(HDjxCoRkiwm@TUxhnBiF1v#TSX+>xATZEmf0S(sj7mPHMc6lj2!^Kjk(U<;~X$ zD~`#^h?@42u;)1baqyIrSAdE6_aBD}?&#at^+)=J*6)dIeszbu2^@UH`oc|g>gxdx zpKV>;cfyVdx83ZX(dNRVX8axlFUV{7T&G2EIu{tq*o{gr z?FQyE9F-YVm*Nzc5WIhwc4M)vPAl(}2TF^AX{w@$GnMj+bF(u%jo z_FdBNbO4~-F4nPjL!;d0`7vkV(g89Utyh~W`89i=YPzMR zV9ypYH*QT4QPK^K3O9ICGQ$^#NY!ckcISl`nTTQs`scQLJH~}P%6D6o##U{XwK;-s z2k%tX5g)SWf1inmhA}FP-`UK~XEis{b+toExFI+n_jd_VLZGHGW$}55hh*bg2mB}B zr;Msl9KzQt@J$oNp6k7@vr`G(4km06X%T*i+4muQ$4P{+KZER1z1g0+J^Z7S zz~WPzpc$1K-QEjWGlZY)NPETS(0_TtKEu6%?byDj;FelG>Q`U{?y%MMpu3T?Z4YN6 z%Y)&Emn$L%;UYew4`3crHq`nDtTk_Xs=tmvNOMWT9P9w}S~ThmxR%#b^U!ZepP%n1 zK+3uuvTf2~2(ux@63IN(R?ZN~e719-mhx^uSpf9$>G zR}@~@1u8XkiF67$fYLETr-F2;fFLz=N=hRjC8Z2fLrJT2mxOc-EdxU{z|h@!QGf4x z-}@KbFZbiDS?jEsbDp!G{p|gmb9&vR5kAwa@1x@ygTA92yPno^J(HMuZ?=!H!kg`C z`Bq0FwdFG1NV3zsB%R-xG@tga!nV-~Npc-gSgwlGaNOtLs#j7=TZxvak_)X`akPU; z1Kf-ov^5K|*O`3bMt^@T3!c~ajsz}f{?*esTV-|whp94a{*2+L5=WA@{q9}% zK=~BL6)%m)&>XF}T>PbhcveVXav}5v{se9X`T|JRm*N%AZ;s5<5x1UQ!=)hU`F^vW z@Ezmq2^Vcoea~Mp6duLjv zKjiFbVnjVOw}QF6<|K($o-E5KdT8`_QiGnwye}5<9I5%?YlLE3Hj2xhE_&zvbGJyz zh)6VHQ-?b_Wa#<`6+-XaFjBNt(awl_d%nd-L{fig77_!R$$hg+m*7WSc^rNNbuNE8 z#%sXfOJV`znFw5NyJLrg3+};(hv(M-IEnpc0$3 zA)os%iF=NH@I*ajzykA&*o%H=if_O+xvtTI`HmqAP95n9+b0wfK*<{kezp78P!LoZ z%@7Cr5#~s&3GNCUhk#Cc;YKp}Ynkw87g#=0bi72Lf# zLrkAKb@9oU*y->zt}@j>k$$kn{p~t#tiqF3(pxH-azXsv%H9AWKF}vwpSro#EVla} zzu)orJFqkzVQGam7;&W9d$%7x38+5+S9mVQ&4ygf3L{QE+C}_h7Ua$R@rDsoXWxf? zTlPzd0avrBIi$Xu3vPTjI}0hK0VAdbj-H^y7+L(H7)tvn9^O4`6J()_~v-9Q-@*-v6foP3(=H3{m@3pbkf(h+FzU7v+#YhgrR9gRcVUu zd7wMQ&5DuubT@rj8;mo`QLR1!nAR^G>&Zgd+h2a+JXFc|w8yN^GjyzSFDOa*OZ7jr z%Cnd%Kkv2LvQ2)Q)~f<0`Dl1R75Vv*YzADm%GRr*-Kk$t_XS)3_=xG!Sj1Bul{VvB zEx~KWww2c+xuF7z{x!u>)KRc71NpG(bK8q4yhdKBt5gcSolnxlOSi`bnPAboYowgt zUe?Okm-!*4i8gcIUGMVIr5z$}rS)T&8Jx^90=aLgmL#!p& za1>j_PthUHR~xD!yH_WhAws-Y)&BP&n?PdWP~$W+dP;ToM;eE_1TjaSh+|j6h#%>E zY{F>HuKvBi^ls9*Y`5f;ekSlV1W^0)_n=czldW`L6ahSSV6p# zoMD56E`UB9K-8@^>%rMv6!3GBhQ=PAEB0HJ4CAh#Y6~gh`d3gR~Sy*(jP?$Wl7$OR7`mKKNdi^T=EVTxJxPt;)1NCxoB_H zD=w`0wMR8iWEDNkHI95f@0u%LzCRi!7mZFfKTO<3qvFwa2_U$ zZ~xI!R@qih`>P8Fdu(crM16kOj=CDEU<&FhE5|hh=dA`!A%+|kUcCH4Qtm|Jx%1!P z$*=)Jrkjs#QtuoC6=HTy8)o0cWVj8h;v$R!j~9~AX`*Mx^({1MAqB33jayr7=Si7D zXYG{KL?ul-1gGRrG`fT%M7k9Y?{Q=E*Ts$o_Er4sJSYZ8I8~1=oJ?;E>X*-!gh-`5T!;@}IS22HL&d!gppg3OLS_9*Te%XwcLw&I+KCLzix* z;QE}wSz+cC0-SX?M{kP+cZ%M?kJq|A?mNVO=YIAFcOJwwtp39tS#0j#rsfgLd5K#> zy-Qlw$@Ov6;P)Nj^n|7TQgN2R=80$F8G+$|KeFb8k@#2SWk#oLII-aubJY0J^i#bMe5` zBOAZrd`j1eoi@I(m-zoGEmrwf5U+W9&!)JZjNucA2CWOf9 zY$W?>LZv1HgZCB+_gzc7KR~l-cMO2L<-KPLjY%Y>op*r3aZi!k0g4J(YerErI05@# zsF7XpPCX;hx{Zq;0K}}<_Cgqase=FPf)5#?BQF@~UM8@?vwxhoUE6eQ8eZ(=czh58 z7O7V@=B>M$X>)10kPPre=Y$ZxO1Jm^h!SoxwhCT)V*45>>rq`q6EqY$No|@H0_saW z*VuC$e!A7UBD#v&j(Dv~{AWK@vz43^{P}-WbQr)VYDcWfRpUSkntlb*M7i^PjcJa| zkOxPpkp53BdC`vIAD&-uhJ5Cho2Q&5K)x{h+D!a4{laXp3#xRR*!t?0e|ilT6+K@( zooriuSb#bjG!O&5BJQ+N@KV0rN!NJo|H~Y|vN6ZKW9$MP8qB<%dOO^E_NL=_z5N0h ztpQB$_$W#FhrQh?Gwyh2K8t4Omh^fK(`#st8NSU~kw*xb*y&_G0KZPjDcP^GQa&SV zQN@hn%&PRJwuRjJyM45jB11Q07v%^bZR(c`!QF8wa~zzpJ9O+?s^$V2c+Q=WC_-=w zXquLgFTK8_m)jy+Zf_R4pR`1^oE;r@y$(r&Cp=-qP(GWCJEu1Vypp)3>94MAd*HWZ zfkP7U)nkfN6zPRR`1-vmW-hjE)6eGlzY-1AgPhueoCrr<+T!nC4DwyX}m@EI3~_@o)b&k0}lg&>AU`mQ%b zEw>L{DaNAyA$5@|_S=!cm4S&0BOH|Z9Jk)XWqS&MV;bMWGMx_M39ropEMRedYaXr4 z2XZ?kJb$TY0y_H@nJKuGS~VT}t_$&XC(P%x&8ZgfwRslw1VHb=fBU2F<(bOwsiY$8 zbTQG*_<7=hcO%ZY1#k9LuUHOJT-^O)2J0_;q^xd|@16w^YXCaxf9^C+2zG4uzy-=m z^e-u!osxpObV1|WqR|(~R}DJ;{YtVhk=iRr5$)d~oS?-7hgt(44!lf8S+aI_#C6_J zdTx>0(aFvQTaSut@Ux0ykiWDF$WH3wG7Cs1auGc;pkxLkv4lax)zp(fn zEJqKB(>xzGRx8lU1PLj^*jA)uYea1(hzV=xE(c4s>r9$_LO$2j&_?2izgR#w?U@A$ zYJ1SPU!uIWa1Wi-8EpTm(#pjff2}ZW^~t= zJ|y^nBc~&Eb;PWrLE4}OLRB7CF;utxo5Sonn070r_*1WY=e8~%kNhn0l!AuX&drg;Is}-c+;aiKdz`5!Jmc`@X*dSE$1K7Oyp~EN7jvY4zulUujq6yW?T;G|wtf zy_V0FzRk`RlQZyMK?S|NDZ0%l^ggQ8qYNv!n@l!$e*#H*ImUL z6E&wiZ}PuU|F_|v;+Yb*Bj`KEe;Q9)GB@HAGso8hFCH|3tBlPZziq~paxa`O$@4Yq zpN%fRF|;s!;#GgwqJw3XLsvKZK|P63FWvfxaPNCyD%~hJU0)YYI_n!UV)$gDmgeN_ zSCR(}VmKHU0{UbYJhk*$noe1b6-9LVlyv}5Vmj_sqy!LD+fhoOq4byQ^nqI@biRt)+5}w(3BPS;CZc!sa!< zeAJ|$rfLnySbk#?uYv|RGCJH%*I+*g0BHOf7tkm9M0mwqtX9v<>pZ2RSnZEn2{b(O057 z2Ld`i{NrbF$^M@muM8V7X#3yI$kIS=#s;~Md& zPY##QFZ*))?`J(^&!^M%4vQp+g4dixyf(*@UfNMs?d>9 zw_~0Wj7A4^zc^k`R-HspzP*w7GE!sUW#(*#sOq4^s7nc@lW-XPQ@cpY*U zK?!YkdvW6v?mlI9OD`4v+q5{CO~#ie7zm-3olEtWTH0m0ejKlDWa}|7|NbAT)Pp7J zM`$sPz}!Zc4|TwgTVdsXCQ?K0s;8O7Pirbc_(U+xo{NhpW7Ir%vdBj6H$@qQI}|Tz zN)axq>!~jj>X$x*7e76IWb0|#PsgN>jD9rR`A`eZTnWA{`G`oC-2exS>&O4qnV3A) zU;kaQw|JBAK#P|(y{7dM9755*?kC`NQ{-s~LLw6hy((5;r}QLC2Q2d+(5t$KK_3E> z-3g@nQ?(S5-&h5X3$<+d7AWT%bwcH#1w;hZV=c!~foKH|2bktqmapc;_YxfQ09(;5 zK@HDy9prMn^~HFkxy%90Y?n-Tt7;~1sxk*aO&Ya`_}z05sBk7=#VkzwKXNDQv-g{! zZ_4ViMV^H5I4)1V%-B>TkegWHH@8>mlNBCUPlZf@{WDwY?fwX$ANmZ9Bz{4sNYe9T z1nkH7uMb70GjQL|OfnGN-n{taGTZPYd4}#Bu-$-Ds3f`CQ}5(CX>CW7u+z0vWt;6R zP^r-`a2qZCg3S2>72$S{l>Gga7xVcQm-(hh=SaE7WRewX&C-@N3!a+iJtJcO2OU~5 zSz9eSp!Zc>hi&6&tyB_=ifA=}`iTSl>p@2Sm6APBU||i6aKQ5;-vzPrt1*+ap=9pV zcn5*|U1;S5gjxelL1!lB!}b!|>QsCxdR~|#y-EfteU!j{Cf73~0n;0`adW3@m^a12 zB#k}L*NX7E%c)Ar5B9}&G?($g1%NidK^&^UOK=1SYD%qevsJAwX#?2mA|1@&w%BJ3L~^(qtWX$+^=7g)!@dGLOs&yUy~g` z?fISLhpk$T#@ew#FRRD*lQ};FX-T8H4b1Z}p8OFv;{XnQXM^$KzmCgz!aVS%MZ=1w z_k!uKH&R}SZX|v~@6+=wbLCXHlMh?LN2N|u>;G24#cXACnFIp!rb{EePDtL0EVb;4 z!PbgQkK8bj(IHK&5Qd$Xr57(7makMFSysEc^e)Xq-_*}c1o+2ywW1lcz7s$VF+tF{)BSP5kuztV# z&yBi=oy>3G8iKq}?%s2li;Yik(Q7Swlg$4pjL=ezWQ*ZA04P4V{hJi04-6wQ8*mi@~7(Az9hoYmmLd_HzDc&zv>kSApAlrt4LA!0&I{FNpf76p&SPAR-wg zKF7EwGZ4#?MBBZmrjrPmD{x;#1nG%%-+_yQe0X8{h5zWq6U=7gvt#Mf#Tw*W$5!*! znj|CNJ~jRw@n-22JQ3HQOXj1KE5Ak`oThEY5z|HmAichSELFB;Ida2PPiuah_u$yS zKIDy|t{CQ{TV}UtqhC0V+m+Ry55qCy%B^p#u>IU#p#fny8jKAGESsrJaw0d5PQWng zR91lub5F0E)$vt1=4af17d#?=pnN>GZuQBZbf^Co`_tUR&AsIw3Wm)jQM-zG*Ng|2 z0b{_JM4Qo!yF>dQqNm=4h?%D*Zq@A`wPE*LsIXdGkZbm7XG(LMeLUmRxh^Q(n7>Np zxv&hWhL)#r(hd2X{0g8tbgnmg%N7CT%`CI8!>Y^#j=t(^ zWJ93O0R0K#WVnCpf!1t=ta>XY3nORqbejph#zO05t%Xz?gtsf#uDCwGgF>)>tk?d2 zK6J}U-R@Z6DMeNKAe=g;zO72a0y-*Y3Xsfqw}3F9Z$A;*xBV3)wSw*h$F@oJ^<_R7@D$#~}+K2^vKH@0763~xtH*rV?|wLw8D zUcyCilEy5C0?Nd3QjhuN=gkr}4!Y9Uu;Zo7#Ge*LJDD2mP=2rHT+~Y-GRIeMWfMCo zZ(_P1(ihMYlu`v|9DDbH5=Civ)bX`gh95fipr5G#p;-pYq)J9M-UgO_g_SY&^N^LCqg;-mW7nIrwKj&SaNFP2Zr4=x;56@#Oyl+}T9H(wY6 zQp1Et9a<~akJp^ZBx!_h*QaNMh*#>KQEcN8O1cV(LAd=?y7N&Opqnu#HhWzH7!`B1 zm^#{<&#_%bYO$qz=|*?L0f0>$Cji3)uu@ef znqtQCgG!eDC5Y?e+ zvgyF=x(7a67HLo7UK?WkuUTZzh;hZG4dhMS&<~Dh`W)=FI?gW4u&%0Bn5e942Xoj^ zVtp{H*m0zm#QpTs4ICc|JYBWbFMM|I8@&;r)t0C;N!ixK0XV~1xp+gH)2*z;;+7C3 z7B#AX({6vp@6mh|P0Z!novjJZw*m~@($)Ns?uekbq`TUvL;hzA)sfjuM0=*&Pjrj$ zF4t?1YP6M{mM^V{_ok{oZ!|u;7_!Gg=#qqOYT zAO2y3?*>F5e=#nYwY8&@;^%W+EpNg0utWWF%ND-&8T@D<*T>vPmkIXWQPPeNe7D)T z+M^4x?=s+xpL|F32#)23*KS9nhk<)1dA*pqXxci9qoj!;RdG>X4)p z*MSdXGL$b9CI3*GgM#<>{u;$a&#*!6(`rw*pj<%a#CzrPqwR~&pZC-}2sa>wRy`l* z4Cd}uy#6gpL*h>oP9B&hPNc);=gr!|{$phYD)7QlWbLEWDf32*|Jb|J;)f^d;dbqj zMu$WEf^brCr#|JT9I$2&?rga<7;8CiHATh7E_APq5qGJ#9WNOkE;2eT=q7Rw_o`IQy%+1(Xo3bvkUkUa${ccMDp2onTM8o$vZRED*5x zibX+cjlCNrVDoX#fhRZJ(>}ycNI|N`?WYB9QJP&zl!BTb(LB=tTz{+(HBpy1s_~Sf ztEV4NP~&!%{@ogcro6u}CA1cxJ^1_;l4D;v>#oqY)o4cX7 zrd6~@X43xZV`EP$barW_UgJiJ(6_5!r20N&TnvDe}PPo|P>zUnek@%sXUfhfDX)`)iZBmawv5!)^dI~_)2cltq)Fz}Rb@P=NOvI$ z#ETaQOT|^2wu%w|hOQ1@6{vI^R+vjNJzUKN1E|Sq`6wQg{?C?t|xGK+8Bzqm1h z1N+IoeyVW)sFW=QU!gz0sZA!+rEF$wupEVtH z2U{OJaK+eYAQBs_Jmy6v48ZYf3^;$Ez$x}&9Jm#{lv7Vy1|L#+RAa0(Y?Y; zQk|6eLao4&yEliAnWcFhId} zcW&3woie*$Xg~N@Rlt!qMe%^HCO0;d{2_0p?#HPEu&%7#%Jz+}lNe(bKfsBt?(_xV zQ5BWS**uNd`0?}e0b-60b6%nP)HxX*Biz@<6bha_!iOIBB%h$J^@wYPR5Tt=I~6qX zDm9YpgC=cq)cLq_{s(nfWleKdV15qA#E8Hg{Jf$h;?e?vCL`BJg+JUKPdVctzj1I~ zVQ+aBFFJpDIRktNbUdJlYcPu-%{VTg*vxYdAnu0FOPmO`UNjyWi6|iXe~Ld$6&E&PODZU{#TITpi3nyLem3#ZHe%KCqY{5!t>FFZetyKz}tSSx95 zULMIrytZ^B)Zi7c~rNFt4uT!@JYYmODkVG($27mJl|^L=7~;-`~20ue>G$ zW9Zj$y_FfB%7V)FDphjT%0+4J7-5Rz2oK_h!CWP(POu$G#j!f(PZm~nOr{1S1aV_3pzqS*pabYvr%IS)_XpbjiS)`CL!r&%uT3)`v!2w_s&Y{5Y-&cwf5NOs*gYtBfa`N{rrZ`!46=##U)ZaBawb^mxaRi< zmQ&_?Z}HbeSmwB+A4hGMDn);EdvI$@({tzrCG*i(+?G%gI2&I)&jiIL%Vo6h5})&B zSp|z^S~lNq;B2qVWC&*l^(B(k;jr$x4n!g}N8dmKa4F|hgAscBN-uR!nQV4(FK9VS zdl^tbqTBo0iI+Jp1f0kECZs-;oX#&s7#`0oc#*ETYW?m9Io~_Q_2^$W%^hGrHhD*! zHKhIp9efa(U>Dwm4!S6bq=fk@Sn&o*q&_I2ylnN{g*yU#IV;!;6$zJHSJSHQJqgWHo{))% z;W>06iVY5T@|!B{4hb4KPBYT>+xvNArR855Kzi-NJ9O-iEA*=F;iVi|oXOxLmp}$D zKTY;b^eYpC>F$!D0LPtM#JC<3ae$AR`9v40Xtc%a(m<@IaJeT?%_W z{nrN0NhP+AX>I{3Cu_far--yC=mn7H6GR7wrjpR&p&=Ftm>DpQ4|}D+p>l+g!w`C# z6iYFld`A;bZcrZYVcZ5FSvF^xOq$- z0r*Q+6w|$ET-@nkyKj?k70{$CeH8SA4?PS_aFC?JNxzcECNzfwhK-_SttGUO-(XlC zO!g=mz02E=G<6eu$yb<1wpyJVdTa9}FZ%0_(_q@&!LG*OEcM0?E`)HbKcI@%rsOWV z+xi#M&~9DNh$cCHpLPnPmkB0SxMR7tm;EuDxW}4yZ6MUcwpmsFB1jqRHxDEScNIqR z)!tQFeT`)_`te`;kGZTV1iMXByN)xB{y4JmEBcO~f$jroj znUcC}+S6CJ%xg*I<}V!FFScq0FK%rVILlO!Dq)XRGqMdAIac}-BJk-ZLq^4h^$<455nY$1pmu!D0jki*zE0k-?E*B1rvc5AuH}cUOz}py*zvUwG zoJ1>@`3sg?A$pT$__&;<%N;{P$}-ih!fE2|Yzxhi_%MasErt&sr)q|-)Lql9DhepP%$AhMDtpW zU}}|kqVV7`QEp3P^!-#Und>+e6}0K-3%i5TyQjjkIe+H8Z^h+TZmvDIy@}0wz4Jk% zc~|Bou1aGw!b|rgSuhcf&iU+F$5p9Hl8_4SN34zyXvA{M!E?n6N z`sY=*km1#=*U9uV*h`|$M@p1VOIl&gw{>_g9Yp797dvCVWxf|3oXMRV&X#alOp_tV zPUm$YIckGl`GzQdQ3lyhbP4P%9Tl))E?emjZW}+d8uuAI6a!<&D*2A5o}HBWQf^F# ztiE1F(F~uKvkX=`zezhuGa|2{=$27vDOI~{M%8CXpDk*c)H5e@wKq6Ta37U#Mrlg^ zsmAZ}m??Jiy+9)}jeCxOr2?F-K(gG5I`Ree_Ae|!&tI~37?Ja3R+ zpo(sNl}zuf zXtVii7i5B^xZyTtmS4ev&5NOp=GK~@nWIC2vzcmFXJ}C!;WcxleLCgm8iKwX?}C!b zcgKaKxuRg?SEfsDSwG}#D-GSGW=O|*pbis7F?S(*AqOrToyXbkI4>m(9QV0#?OC2V zLSk6;Yk zkcjixEL%c~N}gXmKeTSGFhe8<=ke+bEA?eyQuCnw|aGGR8;qGt~ZdqR_C{-A$;nKjIDSN@Bv_AJBDv)kcuTmcMmdh{|%5JL>ni zGSaHuqKr1lxm&}uuzg*5&!NIbO2_EOPx|j=4kp_#s`EJqc>7xf9Pkg*%g&_L<0ph} zF20kCQ|;p3sCJ3|aj6XpOAw=|iYHF+f$XMlXG1%0JJFgS@)XH^UOytA!yvoG3) z@Mofzox9askf*cFW|!>a$Jg^7y^_VHMNtq0b@=~ePg%V2Hy4dzLm^t|*c;WZ4sDrZ zv*0r3HS4V3S>tH)BcK-59H)?Xzsm$7q;7=&@#h%=$S%eklJ7oKtJ$?Og@ziqY?i=p z_4^|{P2>T*YphxbfcFsAeprLyW6)_A3q>SzNHWbU3AKb140s-Y&7o}7JA4~ZFE_=R z_4|{1zXdm-bqOg?n1rU(FO_mWpE-OU(3uBp-(v5lDT__0m3ZAS5~~FKEPn`^`LH;7 zDd)ceHFdo@6zJ)Yc3kmg3oBYugv6dcI&~9sj(ZFm^PKQ2o%jWjn={Bt=uMTJll2^e z`xrMrK&f2?V5&$>fP(|D`QlASSG!FD6T+1CwBKE9t9FJpGQUvKzsfAPqw z^1fD=8G3w$EJCCxY{=Tq6K|t~=5Q{=*BG>r#Uc>8mvv&DaKgSrx;>68?NAt2!sWa+ zX6Wturxm&{miHYv`>VmId7dxm`Z>T!2Igyg6%)kQpHDam!CtOK#^D?(-NM~<$Uq%{ zP_{I$`SP)3f1w9>$a&RHZ%8(!@`@#c=Z zuJ|Wy9)~N@65p_CTT2d?Q$f?L%Sx&7=|sio(z`TB!2-cUqR zn?+}i4pUL2SLUQg!QF<@P%=Hht~%uP*Xp-8E!5%u$4Yc+pMEPfe6mHL)-3IZle@1I|{E`z}( zKAJr`0Z_7{$)h)hV{`Hj6S;iGx-yt5L~{vPg&b2w2K3!^OYWwT1*PZ8{CD^KeCN)Y zcw;>6mI0kW7mQTv$4LO(eYqp{J;C;?s6QFXHgM)V&g}A&J@2kW#%<(jqpgkF6f=a& z5NuDiSxzrRN&H{iuMxTEy^r-YO<9oT-kqeA>6=Y(Wn9DOHT^K96Ot$j`CiO%i3)elMefETVx`?h;D*0o`- z!s22DJ63Q#NVHcoL4rQ70!-hCSmn*cEIiexWDh_6HPd^I4Z#WSdD)OF6jXa78togJ z1h*YDM2b>9ZJ{n3)4ic=icSI>OjD=GD8d9*&e;0(rTD}Gc-CmM7!|^HQy{Y6`aisQ zhR6Nrj%xOz?%6Aav&Kb3p7T@8vgSDjz4E1U*~*^b1YrYDFtS?r2OM zdv6xEiZ)`!M(e^<&40zwG=E_F5?j2<2+=aUcrrWI^NBKDc|9svJa(~cQCqjM4f?WyfEoAHLru}{~u8?(S%i3g2P5c-g27=1@O z%Ky@iH1|rYJHYi8z-{L1`qB;gVk1I4v46XA zxVwHmd%?)Lo%)==&0CGOfAw(Wxx`Gu)FWIK{}-RlmOh0NzSxlFNRjdYhC~N}VbBCD zmJVWen-h=w;xZ_`17Qg*2ZmRv zr>;Y@l()34UbupJm~yr}QQ~ybX}YsT-lmI(UD>7M%VD4fn8>l;M#hiQAW096U77&z zJ@1lbAp0|~q7rOvbf#SO3mG>l=eSTgfi$c~v z6f8Er(q!#xd@@C$iC095w`YXz5+AzWsm|iNu{1qUmg+ma?(z^)^)KCjJ*$^LRrHO% zW+sB^<@Xs4DS1B3xP55zy`=_WT`?xi1Q|=9Ty!Qwg$IS%eFqz#RCuahxJlQ!1s$wx zdH+`9vk4JQL{bCQw=2+7e=un=#YlYcTnR{V+#T``XJaS)#WRPERX-*yfE#y&+qxlj z!5xr(a6~dEY5qPPGn#wH@gXxahe^*1-UI2rLt%Nw#v?G_F;cR3^^q*?-%_4i9=k?f zEiSGuIqS`Mlm$FtuCOgV9qX## z=`U3>O4Hs+4P97tQef6Tz`ntz=q=chmTb%Q5<0&5grd`Oj^NPBR9Tk$>d&+Rjw^T1 z6zuZIPi!Cd*5%hwyhgK>s0w|V54(pL{n?G@=8#D<E=Cy0GT=snlt+%z!3<5cW>K|5o->a+_p$mV%LBTU?A#7RMEJA03 z?MRqCFN2d#xRLlXxa|WEb$1ETWkHZeBnGg=-_mHbwt{o7%L}v;&+6AbYD^D5?||^` z&q^gEe@YkmbMg7FSuS>5k#d}}P5E5MZmxHDW=IA;1SEokM>7PLLU*SJ z>K|ur-+|hj7Tx1RR{k&K|Bf>y(qJkjoF8-@dq}wtu^tjtfw`}pDxDbIyDI_%K;p+6 z8ZD*1VE#;8AL_NuEH~Mtkh*Lei?~8v3Ba`!>!O5Oo z3}U{t&6d_9$F62`%DXsab*%qaDq_`dNg%1!wK5D$iRx2BopzzA$JARxu|oal`}oj;Fo=P^;?wvi^WT}euts{aa|tl2@Y&e z`Lgh*t@C%sxjwYH@m#p}?7Y9&r`S+`a?;LUM!AtA*AxW*C2CCIV=tJkks|T+R*bAP zNG~V@uoRB-4##D7H^;wq=)g_5j;d0U;7BIRu#&=3X*?5yk?tCibE8v5UEu>ra|z%k z#Z1TZAzgbu9FXPvo&@(NMu)(GzBJ&XrkV;tc$g%qL^}n_(tOX} z2ZW$g-$=OyMaaL>9D9ePgY4k2Ws2bVq2!A`fN20#f@|Oy#{>4ET^J~x3+4Jq!?@!JRW-k%zb_N)z0Hnb-ho2tAmqK9yq{5^eV41<$zHaRp9 z&y0i~MTdVm18=*?MvvVD3EV6$_*W`r6cu!w{Vq&lzfTH`sXAoPGD$gUlA=S^fCKq^t$MyF7eLD7#>pj5YM`z6?#W`Zq-S?uRAzT@hoBQM8bf zsAO3WgO0^c#L0@~7NE+T_!7s)V{b_II@@M!$Rt?5?j~NS0>)*ma&oH&%C2@UT(xOU_)$T1!*3x?r4btV?y^{VOYDS90V` zT8}iky{^%IZf9Y|DDG(^)!;r`7K61v$r_lE3BiI)$(MZ4H^zi@fyR9zVq9~Zj-{P1 zcE{=!kxWU<8i&pOq6Ti2-X+;`iJoZSoIpS@zb*d~ofMv@es_ zGU!8U86Ukki-xWc3?EOtPEKO}MGOTc{*>)65}E41et4ovMp2mRcVasPWW_^HL5V>d4*r^4-sQ;k_rbvgTz{#tQvtq^VaX-CquX!1Kr*d z7NX*6g$Btze>6jW89Yg;fwyBQRHg8_cMrPyEWl>oG4aBU9PP0smqj7(%{Gqv{sd#+ z=RC}&h4R>&C9!4C&e*tK`{+3t0#(_*l&&=|hHRQ}%~^BKX({<$`fHyl3nJUXM2_v8 zT0e5EKWvt@DH~PnM}r{{E=E{|6|qUS8&uEKgncABT%Dy%i1reVIFzF7V2HIxypiQ* z2;{IaA?q&SI`kl_D>?9M$ii-);pmr7gB~6?`$`1|h_3z2qL3S4ZXsN{Og!fSOIKRl zK(9k3J=b;rr-6(NlfIqQdAOcGQ#wdxM7>jt?5oJb|`D+QI1V z4)4(eUOXT$oH}oo@V4hy=AFI)UU!};c(u#rYWUz*hWN&ru(Z_My=Meb7?XLluiUfs zW;rDx_OV*N&0(qBP^72SoT*4&M%*1!JFGJv)_0`eRfLxFo6*Bze3V1bJ$iE)HOxM#byc4gogUfk&@c7% z)6MzX)&V}z451+6{n{z3)^FNs10fzJH9OY~r5vg6%lSl>Deo=3rRbJjO4QhuzBrq) zTsw%%ukAmqynkGo16Tj-f~FbrMXnUUm_MF0YMp+$%NuT$tG}3V`2c)qFVi8fin#}_ zk`s|Dx#&EQ{rzXZ|C(POA+5QHO%ICnra_z_neh9E->8E?43GuUXqR*u|LEwjf7adt zRYWVAzo2OZ6?4uW3Pys13-z3LIF#8>@T4{dJu6qMhTnvWw|EAxYI#5O5o4Zs$DMQf z>~e!fhVb~KAV~MHgseyGc8F$i$dvn9Cc8&Hn7FfGw&-h&-)OLlA7PbL3L62lHU|~$ zVZXR*4#+;g69PG_2VEVkCpX6=ut6jt{ltK>?QlNlh<#1yh%OU>@kYhYp`6H4CEe4M zQbo9Wvdan!kxmABnRaBg%lZ%a=E2_uukHG1y`xwMExchunJCQa)nGwkX;p2feu>IM z{cKsXZ$>k|75&%6NJSK+T#6v1`oy3f(@J6b5HV2@Q+6`W+J2?+?4S9bh=(a*mf_ug z`O{!9#e}&JXSMgMgfgoD+zPG~r5Ugr0Nd;T7{}A{AVI+LCm*i%4)&5R{;~Bz`%vrR<{nZKtnZ2Q6Wg7wuuBhY&uvfpGz=Rr?BIImb#gwp}aPQufFlsq}%7?hX zk{ft;W`-h^P3bR^Z!Ec7`~IKf!b2P66LQ*QpmD_CgN84@lYbZg)I1eV$7^(sjp2U? zNCAIsxwir(_v>4HmKD9tH)3^8uXO+{*3&6};N~nKN zTWBHc=(bN2_g!Ej&YZ52NoQ4kYI#oj0rt^*!f3}-e;PWmp`u$UTy1B}i>IF#83Kqe zh$HH9Nrp;a1Eu(?4Rp12|2pzzdw6O-KaJ0NmR;zunJ`wa7K9NSPS?0SlJ0aIqm+MY zWW1nbQjEC)7rBLkZB+B)F|g3wBpUj-$^|^k9@!xb6NyG;WcjIN@meLlzx5fmVo7w9 zW4M*^)LMm9cJUAcnp~s^UtCwhW3s7(FZ%BOxAWa?CpPw2!#Q#$I35toOz{59@D!== z_vq;&<@EZ6&kZv@3;Rv@2?EA3ptxyAxl_KYW;os8z71Ji;6~+YxhUCD-7n@^gLZb# zY9%9tWLL~zjgcbg4!?8N{pv_b}J_x-1DzjM)#;#`o(N;<9o zImU?{2P7Gx$#FT%9`o0iW-W?`y7kU(fx1p8LLE_v?N=&+{Qw zyeThRc_*Oe?Rz~KGv&69PM@RS2vdB?he&}{JbFDVt|@A5vh6Eody6%>Sq?znfq9|B z^M)Afs$Hd&ki~{GBHQh7a@*xGDqp4)O&81UhGp!;>Zl>4^ecrM1qA%$7L+WkP9g2W z_RI>5iK;6^S2|Qkv`1uDRJ1l#I$S&AwbMplJd~$|DH4P(TlygsP)2gn_wIy{Rk;=p zI($bS--wIt75PJ<@U4CFzw^sUp^wFH{@&jE!@k{?*w&?<;4DYeeESe=tQ$e=uU-iS zED#RN(3g(wC$oe{qD}R*G=_{Wd{+azEpQte|1j(Y2B^>Nju!|5OluH3vHUPs_FCR0 zFXFmWxfwNE`t!S)1rt*I$M8#nnQnsDjlok9`U8$OSowQvrXzT_yR9RlG~bZ-j^j_q zB(XH3Pb%wPV%Y5j_^*%2Iy3ExZxijZ5QGi6j}$bwJjL!P6X;L zeOP2sXI@^RC-oz9Z?VYJ1Ve~w`uT7GxT7laN};tAZQE`BZMRhno8iVLucAMhV9ZFu zPDO|1E&n7$nQ?R&hWM7Vjo>+kfCGU5DkQEWR&GyeU>;PstnKZ-Vv)J99qD68fqR>% zjxFKYXa5@WKfCcnCYl_!IhvY1g>FC)1WFct^jtj!I$! zFKk^Ib_2~9r{LL_?eeU@3R?K%>n$ty&7EM~tTCz{IaWVhV!N%L__rBAw4~5c!ExL@ z+&6WKHT7NE9a!3ws5I;EY>@V|=!A_M;2=h9%9MTI5sv)2y89Y?qiFlfMMBaSkpk|_ zU^nXNklxnU@7E>XT-c__f4ttU*3`|^WK+7Quk-Mo6Nfhu#8K>SBPJy?5SGV>m=he> z*;F)~Eg*F!-9756GP7MBS5>Prjlw>_VxsAV5u02?zC;##4Cyvn=bZGkmC z_;KAa*-#D&Uo~%#)bwegVDQ1xjMe=sih=vRl!%tZWSFMdo~-zI;=_}oMevxg(vG5v zT8SWaulpJTd%CAJ%BwC^=>*LKsznpBPUAU~^z79K)4YSE<$ViF*k|Ve1JX^af4##7 z%q#5xiH%i(65#1MoMYjgA67SM_f~?XoS?j#MQj@?$FpKpp8EV(T6ImBE>Vg5Tq6=R z<8sTL{AzkreY#7|FJb9HW4*qpNzDUQlP%y$*U_i8Z6q-YB2}0#?hU1$JiQYl3zrlq^5+4r0t6@IN4x=$=_* zq)wiSR^@ZlQRtm80BOw);sjc%EUp?n`?6@Tc9iH68wq;jn*nmF^v*&pf6^9nF58xU z|Fbl|1}X!fJ{JymYn^uh^yOYgT61TXhmm(O{4oZHojE>{ET(OZA*d1RZ&WXS8`1ihCA6isG#9KjiIbqq6?zTN1^P(WEiRm(r#r| zd5)B&GW%!?eBjWFQRMh)BR7RQIFWix->|=H4JAOiY&7(mUm`+Rd6G_|?^T4L&gO0W zq2a=68;I`84NM$ul>1#=mWZi8E5QIO`cIsrwan#U&11^ht0$+; zX$yJ8R|qcM!glN2J4Qtu2Et3S*!ZY(oBSqK?Xb0`9ht_HpQrO*-e~`;vNJPMMHgdw zo$yP-$802Nw_UBXpHKkw`8D&ti+h9?d%}%8>DyfiicL(bR&}>q__>Tv zN<^}marzK(B+fN=mwttBhFQhHXWt^uRMb?pLuxDLNZ4kdfMod$jueBckm2DDt~yAQ zIyv-gU2{o|>wxNudG!l9i_be}3wU74A# zytdifT}}&+%tFsA2c{-uu}2&$aHOBXDj8)vhT3^Z4ZXtnMIMl7x~`g~%`jF==7PY? z9}G54=(Ke&&XbgFg#gtg(g3_LJ>@d`aiOp!|+9#j$+Qyhrp3F8m!^ z6Yte%wuI?_p4u>-d(cn=SYC-8zPxw??tE(KMF!kSPz{=!SpNM4r=Jo@2LNromvK_a zzX!$E))|e<@Yo8#o`)s~w%Vh=^uhnMDvylkMi^%A=m{>3gYzaT6*bB&7jazvB-|mr zSyy1m0`%0P{rR=FaL(XCts(EJAqlDXAK4>X%+^6H> z9J6_`0pX$#fpT6G@frkk#l5u3%Q|BAw~~7Oxcb9Rsm%jwUp4_+cB5bg#M*;e<*gX* z@L`*lg!QVu=Ka+UnwU!cVtUv@!qKK*Gs;)&$}9Yl`zoCMCFcV2Zd2B%vynqs%yB5j zXEar3ToK>fkc3rcrM$9tGPd)AsFsBncRPwMyqGV%Z=0n(ERfT!R}TJcqc@)sF)LOQ zP-ot`KXD;3evvuA4=Jx+3ZaVNONr!e%QCC4z9z|VBJAxN)y8H!_UaGUf%c(7S#5{z z>ASBjHZ6rbGTM=y8GkVF&pZ!R*@jpMPb}omgLb*N((Zk;!?=I#EyDg|o5$510^cQG zKf$~DB_Oqk8Nrzbaq>R|Ca5rFFojV3bdlnEq#ZpkbC@q2m9NlV`~Em4*%Dc3ez}*a zu~g}B_>%?&=eZc_3Y^H!)sCT(OpgSE3qh2RIbT2h6n}fGJ9KzYO!Ia1a-8PWtPShS zS1(&04wFIFI5aBsRK7PET16ud&7)y^=BOtRmvID zW;SD2eEZBVa&Gjp6#AjudTVs$trA%;F)ybok(fxHG5Ztg=sDiDw@D9kU<%QKC&)Xr z{Y|FMlR(WpoRsKHxE%99!X$vW>m`c`(-9gMRj$ajia73bnZOtuyFBw@&KLUfF#hgJ zSw3q`x2!cZL6~JONs`9|nc$PrE-0&n=Z^a?&skka0hU>(S$NXNbbms&Ed$7*2J_$!`5sXiE37^ogKKUhL~j5ENfN2DHAcll-_vFeJEz!cy(t8Qc=Mp8)c((Re^JU zDz>jL!uu~<{QSF2XiopwDGYOrd-U3wqrWL1Sm(;s7l|O&l(dd?bUnJ}nm7b*iFBSo ziC*?o)i0*jbgI;k2uu_h5oQFt9utx>;c-m&XCO9&lPqxai5NuX9KB4KMQ73{_#pE1 zotC7Umz40p-2LeRS^SaF3)x#%c=$0#+`&jgX~n@xoe$?Xzd{yyB&ObK z!&4014|Om;A7F`r5?4^_NPiUUs7t%G!P2A0r#q_(Kikq@RT~4^#J$)+D z$>EmKy1qv?F`=X;8q+!*Y4%r2r)JTUz1gckb!PoyXT;dz z;8=VX{C86H!vHiRil6{lL?&Uv@H|2L6EID*7RnWtJ}JE0ygxa*dQ!~o$!a#lZ)lS3Tg=0YSeF178F%ch~O+HObfhWk^Ox+b4{1=0%AW8RPY zv)RUF-TeWlFiRWvdA$SwLWE08>8SaA9Mt7GN%MYQZ5#NtuWtDG-Z~z}xI%==`F)8H zO7n2&`<8m;M`yHperq-d0Mtxh!{QvW3dwfL?bk2ee+aqgJuX8ssMjZtjYgW_lDBGm-^c5yd8^W?3>bcX{ATsEi{qPctA^gmWvgCN05k$w5Oh;VuOF z(O&oFmC)NGU$L;BKzh4Sq(K}6OGrY?Mt5-?h|_;{J<8`F#H4uxex7EIE*<1cQj7jm zbt>2=p4FZ(e}HeTx5oO&f5HRse!#yAT*#siH5<1aZGRidIf2wTYOoxsa?;xAWtIDa zfRO*3=b>aYTcoc%=COrdUv%rRSifYC3Ja1}8SI#}o^Gu%)7zjB3Fll$lI?S&UK7@c zw%41I(@{I6YzYXGyp=#ncw@woga91Ut4VJ#VnhP7KHBGkv^x?EZF1 zHH7Iuv`1I!tX?CY#p2l!PR_$LG(UvJ_{O|YI-H=D^pg98su zgRuQ+DtmE|gpjkWy5wDfYj6D9q?{UaEZ(HE01a00^&o8lr;6Bf1O~X8l8R%MzmIlM zXoM;QKFiDz-_+iJ0b-G17AnH%ZWDrUrguJ-^HV0L`g(O$$#8Yr$F7i{F-Qg7BX6z$ zz;omPF3b*A{;q;$1#=hq`T`?Wjs@^)%JMZZA8M~KJ@6<4Lc9YTyd-(%ZX+AvWw$x6 zRl`)=e7M}e)}4d1$A8Ui->O#DDHKs~C^c?X^Ny+p*PC;~VF19mj`6jD1Ubz4izI_% zNU0(<=`1%0nPe6S8!L3A&S^&HWsdIHy zWG`U`0XjDKh7m_y(lDMwuRo8CkmI5_|4#y$jza7+tV { + const stack = new cdk.Stack(); + deploy(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check iot topic rule properties', () => { + const stack = new cdk.Stack(); + + deploy(stack); + + expect(stack).toHaveResource('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Firehose: { + DeliveryStreamName: { + Ref: "testiotfirehoses3KinesisFirehoseToS3KinesisFirehose68DB2BEE" + }, + RoleArn: { + "Fn::GetAtt": [ + "testiotfirehoses3IotActionsRole743F8973", + "Arn" + ] + } + } + } + ], + Description: "Persistent storage of connected vehicle telematics data", + RuleDisabled: false, + Sql: "SELECT * FROM 'connectedcar/telemetry/#'" + } + }); +}); + +test('check firehose and s3 overrides', () => { + const stack = new cdk.Stack(); + + const props: IotToKinesisFirehoseToS3Props = { + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Persistent storage of connected vehicle telematics data", + sql: "SELECT * FROM 'connectedcar/telemetry/#'", + actions: [] + } + }, + kinesisFirehoseProps: { + extendedS3DestinationConfiguration: { + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 55 + }, + } + }, + bucketProps: { + blockPublicAccess: { + blockPublicAcls: false, + blockPublicPolicy: true, + ignorePublicAcls: false, + restrictPublicBuckets: true + } + } + }; + new IotToKinesisFirehoseToS3(stack, 'test-iot-firehose-s3', props); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: false, + BlockPublicPolicy: true, + IgnorePublicAcls: false, + RestrictPublicBuckets: true + }, + }); + + expect(stack).toHaveResourceLike("AWS::KinesisFirehose::DeliveryStream", { + ExtendedS3DestinationConfiguration: { + BufferingHints: { + IntervalInSeconds: 600, + SizeInMBs: 55 + } + }}); +}); +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: IotToKinesisFirehoseToS3 = deploy(stack); + + expect(construct.iotTopicRule()).toBeInstanceOf(iot.CfnTopicRule); + expect(construct.kinesisFirehose()).toBeDefined(); + expect(construct.bucket()).toBeDefined(); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.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-iot-lambda-dynamodb/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.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-iot-lambda-dynamodb/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/.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-iot-lambda-dynamodb/README.md b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md new file mode 100644 index 000000000..57985051b --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/README.md @@ -0,0 +1,88 @@ +# aws-iot-lambda-dynamodb 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/aws-iot-lambda-dynamodb/| +|:-------------|:-------------| +
+ +| **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`| + +This AWS Solutions Konstruk 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 props: IotToLambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } +}; + +new IotToLambdaToDynamoDB(stack, 'test-iot-lambda-dynamodb-stack', props); + +``` + +## Initializer + +``` text +new IotToLambdaToDynamoDB(scope: Construct, id: string, props: IotToLambdaToDynamoDBProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`IotToLambdaToDynamoDBProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new lambda function or use an existing lambda function| +|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| +|iotTopicRuleProps|[`iot.CfnTopicRuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRuleProps.html)|User provided props to override the default props| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|iotTopicRule()|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Retruns 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)|Retruns 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)|Retruns an instance of dynamodb.Table created by the construct| + +## 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-konstruk/aws-iot-lambda-dynamodb/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..f1f3a0f056e318b03a661112a8ec49c1777d6f23 GIT binary patch literal 80537 zcmeFZRa9Kt)-6ncAR%~g2oAv=f&_PWE!+w2PO#t*+}+*X-66rcUrbF zwO)R_aZnT!gs7OnKX`cqZ!50h00Du8_WJe4G;`|ZPxv7ugawpc-yEmIf5R7P9xOF= zc6M0zg%*rl-DOO*g$w4dK|Oo3b*UAD$GbSBJ+W=kAoPnDJRDU>pYhe2LJ`Q_K( zhvE^{oNIki;NUrjng^vjpKnxDTqci?lUD)v48IS!I>04|jRzS|a!zV^z5I}5|9k!a z009D2I4)`<9QYp)@ISgTB(kh6XMflxadN@=`}-@~R>Aj5WW#@l@x?()vHb==j%ny8 zLjkAT3zwJ-xCP7^WpdSRCX^`8}M70I9@)=NN=Br|Cqx zJ2emIG0MND^zz-egNVO+2}}7P7W#EoNnM7qlu@5#5btUvk{RAoV8_n`%o+sD*1q;^ z5ThFrT3e0tUQUG`mrRe<6!379%|zkwEaoYSZ6It8F=AxE`Ph7_^Y8K^d*9QnGJ1Gw zrZn*W@8^Gm_8SWFL#Y=;JiWv>Xz?s$cxWl=5?w7ETtcP}Z`$p@P zC-CZEIiPtw(aA+uO+f{|8~pM1fpt$8S$b|T?U;|~+hnU)Vr~CQeMvSv3~0y(aW3R1d0dte$H`e zY`{RewoMLWfTFC~Yca-V4mVdkmw0-sitE_VV0GeS5gWJ z_TnWxbSrDWm}trMTb`=8o;sy8_R}8a6zuJ>tle(amU@1?`PXu5y)Jhc+2fyy5TdGU zDzL$_lsL3|1kdFED9J3+)VTo;jv{k@LXS%e4U93_3h*Ygew#rXM(MU};1N7_J>>JA zCGQ4S|J0_Eeddd+A5(e1WvrW1LCK8r2<_KH64oEl%(v?1nhwAsi!u)$^U;4Hdi=>3 ziNVZyhV$qzG_KP45tTO&;-@?OwQMIN+X6PnWwbQ+6OpLyP%h4nEo{Y==PrkEX;B_Z z0TT~q;0tm&=RFe%x7l9;)YsgezS1aK%@~V2_P`1Yb@1fGVNKfWru*w^k^E**Uz%qo z_6jn0GQak2T`IBYO zf{PyT9H9Q-LC<<)W3eVb|4XuqEvejVGR_~FmI-GpGUeHC!4vNxV*D}$8v4k_-x5Tl zl*refQ#Dq&d$-^VCAR*C$unkW`2AP*_iOfMk4jPQc&D$1@z<4A_`2|tk=L*9v!dW= z)0t0bW09;?Z)$!hqOco?){#^;GkOx4_N3ON&Ofc|m#Wze(;SIz=>)Gc?M};f(;n<+ zv9D)5w|#AbXQ|m&o8`Oq2Laaj^n1ECjY&nSI#etfS?)=!DS2@l(JQShkuLxB;WRHF z-YOWv{<;yXV&OJGOG4Ad8v;J1^d{0A{BV)MsmNG+J+{uKjRylD?WkFqvJ?yEZqKsG ztJ5}3eOIl~J+@2>xet7wOY9eZDnNbwmX$$$O{9)BKg-}YdN5#g`M@afkEdIGr65Fg zR`Nf5L`GB_M>-r6rZEg~rpR)5P*Vn_}-!-Ce1TG3VpeWUkzXAN6T6 zv?Eo*`1UUqi_e(Ti?4D1O%@#RwkGwdU1qX>$U`KiIA&<(J`WAO`(^zZnU zdg7?!WB|ASjg1SOe3^3)rqy5k4;KebjwMoCY^{osa=m$SEjNDPzvsS#R*L-mff>xK zi?jM*sl7Yna_4Bj-m*LURj*ax(r=@P97AQ0aunxZF02CO#cbb^Fa3uji&EtSXB~QP z_M3wgMQ(c(2I_Wpfm_g+t}XZ^SM=)fu=Q%Gqji5C+F)6bU!F=kQ?jzeoHR z5%3eDz)6kc{HiUfe$L$1_!YFhV~vi`Sl^KGw!1~g;{)`z)U;6O#1p%zR_Boo-hfvx zFu|qzls#8Z?7!p&f_w3N9(|c1>11!v7PR2?@>ipFA`q2I#`TY^@6+}bXfnJxS(_0& zGpmEAIHd~P^kd}iA5kMFS-0l)g2?C$Jbwq*JcEq?4W`;Fn1N_ke+U8@jz>|47hmyI zMq=L8bX*tN#&xgRXzdhJS5x=KuneiK4@C0`LFwQ!6vQ;vA10Kq2&A&;mHubV&4M8M zEx7fPWd3Yh89hCVE-l8BB-w^ogx=KHgSGH^?ARDn>1UP-`(x7k6ArizkE@E4>4>%g ziZrZiI!05B*6~KRv4P1(To#%d(}2Hu>>G3of0h^)wf315`d>yT3pW$5rj4Gb_>7kt zD6=_WD?OXS3R;V{PFpN=yvU~6yF~_sRJ42CZWKRth(seBn*n0mjzl-x?0uyl6e;gDz zm|wg9s`YGazqtDT45*Yy$H^(%NLZSx&Ngc_woi-_h3IeV9xalxRJkG{Vb){f$;tC$ zG|*?EqkR`ktlZtYTsd0p=SDo6OBNm6I+CE7_viYj;>qCtsS)$%9_Wg66~tbaf;ome zCs;NO;eU7$_01X#ezx8pES>4!!1V~l&>3XZj?zGwN-6dF6FDK7h(;Y($oC(c2UYe} zKly1CD<3XH!Nzwet8qSpT0)C$q>?uVWCKgye`3t6C@tbr$kE5K%v1xt~^lJklv-m}?4MDif+ih+mwZk<~8doihfBL2pW%R3@g z#Nf)llTL=+aEP#svxk4FUd}bK{ z#d^HH;A2m1)~G&0ddv1^NGOLi4g3`FTcg}Y!#Y%$+sfKZ+cTc7r?W~;S%txpRn@Z| zb0s4^&}_BNdwGYWQVZm+k2C!bSUQLd=nbzZ5+2i;j7j?4AQGp6@!w|X z+n(_fBIrC*?m@gzq_Y_0K@6J9SId>w0?W&R&-EUCNLHQ}#O^h==SlvZj26NNUxQ8a zOra!Md;pqEfxLs&@DZQHxRmZ1mEF9Y5anIMl-zaXqhCnCP*4d)LFBtcR&BgeS9}bU8}c z+S0SaZnLq+p1m(=>oLL45F_{)&`nf!#f@T4=6}*;dMhGKD0FxZkW%M<>?glIVdFMD zsZBi;l`a)(@13Q9i?oOC0jh z@?Qyjno`tYo)7#K0}t~>ox^x3-{}QzNlbwYn3EG=I*(gd(#(%oMPD}r%eD8!Ze%N= z*la^WT|t*0DYd`TrN8%2Hi)H1c(3}{&n@5E`F8Qx>Q$1#QT$A^gVU_d@Mg8`?#+=Vf~mfwM%1 zI=x4F?1HEXl%qd%LR>l?-iHC7^v#7hbr~7pS zva&QcY>vZ66WV3k7U<1TwW%a>0ql-|N8#YU;VQi8fx=0+arU9^eY;HHrR$)1JG?{X zhHxitkAqiH;}eWbF=K!zyU@ly(J}JiNw=6G-+1fLdXUWTUIwk+YP5m(x5}-M@IGReEA(t79`LQ>P=Z;-^b0t_bsvh9(d9y=p9T%^`4^$Y^va zCD3Q^vYex0!Ww)89}%&O`gR@H~g)DTR>>K8HTl6(G_tN1#>tYK4*d{I~WDOxoCW9x3mTgBU$>&kg9^VeZY#rLC-x4T# z#S|fuHuZ@{gc8*M{>0U@S}%WFPu((;aCEOHw~&L@jf^jm>Tg1LgEmCTU!5}P|L4vE zF(mbs=!c9iFZjQ~xU;YXz*7;9+Tn zX{6?>Dc2v4iexUxZ+HJ!cH+$o(a*+iWW1^g_mFQMGUM7PIrq-}T+SSDTCofdUa%*_la4jv zJej136+OQd8Vam3IEQ9(yxK}Whn10d*T?rze^bnVx#oXg{4WA8(H$3Lcg5EfxMc!~JDKf?-Vm{nF#uw)ywN`jV|*iP;}lsr@D$l$7Qp*YApdtd!Py4VN93mG zqot2f`Y$iWIw*9nJ4?dbLaBd<<~O@7R_rKdBQP;vZ=mbSHj;$yII`shJU8x>$#fg8?k1tJxx2e}9T^x*$=# zlBPcCUyQ>osE11JL%+Gv8}d2#za&IfrG~a))#b698ShW>5-lOXuS75nkCo7dQ?g_e zyuBl~M{6iv$eV~=P&GJ;V8oO~>Sc#m-d=DYWdJb!W0rq!o?}VK)pkU6ux8}=I?T#i zN!LZ%(c;1!nWY!O@I7RML1~xEF8jt!&S@x#_bzCsw(9>i#wN5oiR$EFQv2D1cl3E8 zF|a!zg0`#fgBAgM$bHK*zb9FP(Qomx5rWTr1A9k}L>qsKmI>x zeTn0D=_?rv(D^2#`X00Ulfk*XW#HyJBUgbtyuMgJ<*iQHxb*;6W=AsB$-wNJd!Jxn zY|*DiNFNRbZ;706h5yHh;rxnaPbglA$x}Tig+s0A%hZ-Sv?EU>FXO5k-7YGj#+|%H zt==LHI!Ovp@GwxxkkgKmIb88CU!dz7Vqfp3VAraW=^A*U{r~H#f<+S#RY7&rUc}X- z1ZvP>c0@>@6W3P4VzJn|#V?GGl`X7oT^P-0sAK3K--8AMy!0K{16K0`1Ev3!LzROY zsullFfYln=)%mLqP7of)GPhbLCIEr2>xs+q;=$Zw6R~0g=Qz`967FuaYM7<0CIv{m zz=g18g8trkcOBGY@qyJKHT;Cg@?W7^Rqso($_1O4;C1c0kWiCCmTKe`n1|?E__`?! z&e0ckR5wPe>ap6?tt=;rq8V7~NL0B~e6RO(T#*C@OfnZIbTD0Gh-&Np=a@U%o_|jl z%Qxs}I(pTxbn#fjoZdZigMq-r_7kT<^5!w^5RE0-P>xhS?$_tu$uk&6oQ2`NPC3tX zP2N^CeiK znI~#V!xat2fM&81Ow@P^X6HR(sqP%#4Xe6lDGV_X{f8*BfN*D9xxj@fmRCP?1>+0s z9d4ia1$S&iyLH8xNIBUgOQ(2Rn^!hMj(InR1oEz?a5GWKD7Ru58ndBmSl7Q7^bq~G z#6M|q7%e4U@-VYaR-*P!;GS&8R50q$A!$;bZ zn^pq1_5KFFf78ToHpq)pp1&5`w>_%s(|d^wV}fs1Z`t|KLw_T^)v%Pnw209P!>bN~ zOV~HZ`R{Z5?~DIM;C~VL|BL_#`$cRWAB^3_4m38zyFP7hSm5CNL~&>5Ad-!mb5M@p(@ zz-#t_ux7B_9@kPNksozLFz(=Ev-{$~NwwT2-<_^s%S~dw0qLBguAz73TZN1yDcO}a zy2K@1#SkVUsQH+b6E6#>Ip*_&Gz$A1QF%TKapZF6@-3hka!DzfZ-G;p&ARx6aLY(lqGFR zCWUapHv_GaJr-H?#-4S0Zu0FJWe4g^zHmOkN^s0(;eB1s$&T_=Xt|Zraan|GuzzW! zcbIaWS1cE&!bD6$(=qwU5(>?5;(+lOdqiB)(C@tW@Yh6kpW(ic)(-?BTLdUxQx;^P zzbo@!NW2B@F2TfrVY{#cqv&S$*aCTlx5d6pio4?k5@8ee;~XSHhlZ7FeFGJK@W^}25oA4GiVzoqlY#J}i z`H_!KJKP^Q)0450XSW)ja?y%8&)O!hKEV2_KOBR)&UNuvSh1NOvK*;D4YEz!0?6O@ z!+$ly9+A7)r0{&S8Tr0R@vzWfaG!88`E-xR&^|}-T=%Dd_$BZghWYPubd4z+3{jFF zmVzP)i6S|z%41v}y6d6544J~}q2fYxx(Xmo4P1GulANDZLP>5yR>~3mfQxiM932tHuMaxd9u1w>UpOBH#gb?n- zdXExIbu^skYJhs|Y&aIu*4Y{za2htf4kfz6t4HsU+G`xm8IgAhWmFCS6e76Dz7h7A zm{21RRvu7p-4klrV$(x`E}f3k)j-qUi_0`&b@zLOpB=N3**fj%=qnO7wsv)0dA2j^ z$43cEN}0yjVE^OY#$bm`b@-Oz zx-&xaDqd#;LwPIG?UQ(I%x-=?NS?(?Xekufd4%9xM1;|w>0)MTR3A?G81qtd;&?u$ zD%-&$h*nrVBdm{_^%+~Su2E2p9?Ds~UiZ}_@ZAPp_-!F?r`KZ9rnGPE`gu~k%2uK% zh*MflBsGXzJDOXF3G&<=hAW;@dJ0Gu81FSdgoMT{(edBX{c{?Z;{>-N&-?X@-&g}6>j>@^p_g(R& zqCShmWbu3fgJWJHm$vwL#4xd4sV-5uV=EC zEK)@0y`2McQI~HVM`_U-Z|Z<^-IGKEvCh*8>mx3@<}w_M>>XM*ARCkXDcmgS#t{GAg_&agm+ zx|R3IgW-D`SLjrX%~v;Ks_i<#ad1!a?Cd<6ggDACa%W9l<{5F9sRB54jPQoOm(raM zK$AmJ_tY<%9j(LC|4?dFZ}Y8Puu@9{)rqK+f!(L}*o-?nbDQl7bAqI-6?a*X?$%UY zq*VLRXG;veF~+z>*_}|UnMhY@Rz=5$CB5i#DTJx-z*-7R*y|7g-JTd9`7306o?I&S zmf4(0(>}cNN}XklovU?Y%3~~ieTQah7z@ERp|g;mq+|GfD2OJr3cg2uhSlBP-%Qc7 zM8(uP+wH(MzDb(X`!xVKntpNg&gR)ILX+z*XI1(y5!uws`>rkWL7tArnsUQMc%{LL>{K#>5pP(zNBb zXzhVTaO~BJ#QPVlN)O}&;)2?J4IKaxX0)wQXCd&;VVDP&@RW_1&W^YXn14=izl%oi8q3sJXa&HCPv@ZEG#72y9UOiTt5&&YFm@=m zsnr0)_JV16fCh+reOUIen`IPU>hBL4YskP#8Y?szFD=tpXgPLYxB%(-zVH?_nWL9l(q~wQqRYf&0G0hE_-LoQ8OHFtA7kEoxqHw8bfb3*h?Ky@Rvtd;6(7r4k zPGZ@ob-t%at}32Mf&!$eG+3#+6iHVDi6*)fX`@?iX{^>Av3M<_v4A9Qs%MDsaBC%J zrNSpV?w_nK$*rlaIK`l=>)HH9D=MarNm6CBme0rDx*gBQ3tv?WVf?)H=kK1ji7E18 z$5gIc4ES37*JR0KGK2{|pL`7UJ#t4LX?7^^Z2r95yZb909wl8{)8+*)YRTK5#&b@; z(!fA#4Fz4)l;c8Moa&L(jV%LrF6|m@mQG!fjLwR){y&WlUG!vn`4OQc7(&(JlFpJN zK>YoVl^Y*@^(6DNgb(utT6ZN-#;09%;jUG8=!qg z%}9TgaqkNrJ=c1`vfGUo<&!$An4>A(R~s9#vfy+$=g?K}WV!Eec-8G$G|+8&7myOB zN5Y82LZ+1cA;EOPCE^ZlgAHiH@#)XCCZPA_nP7eLM6e>%kyc!i?E4ZW>`=O`=yb3$ zEcFtJ+S*KOG?(IH%2LMNla%CRSoPI9xKR|u;`3ktBc><@S}T-tS5xq!5-YHpxcJ57 zMxv=JSicp!7=h;($3`EXMlbQA=?#NK%HE z8DFpvU#a#vAjm>Tb5TT1{fh3^D4Pyx5EkJwiQ*-Ha}TdP8&1I7I!X?2L|y#aju! zklSb9?~w(TNxqj9Ri_D zOqT8_CLb(4b#9icWuT9J_iLbEl*U}w^PU>QOSfOpno72R zO9@Q#9L8|eeXWbGnPnIyU{{QZE%4O^IAvf4w6yuXy}qHrepZ#y1FsPXx}Xi`R~lQMjD;T&Bco5bXvBm zj|U>ea-upzAE8x}l@}ie>61Lg(+06$b@#C#WKMvvSu>G>#G+coEugWnWLT%Kx#=C< zuz~=&^IRfLB>wN+qJ^w3%>@z+A9~7Ph)Ehj2C1fmZgp?hWNXUlcokGiO0BO5B)D97 zXDV?A=*0QRG48L8w`Nr9cG>Bj*Ls>~wZ|q^E>w9B!bSSPdab|P?z~%{TvHctIfo-% zp-%7Fd>_nvv_nY5*S~xfFrM^4*>fIIw}sFN6M7mj5<4%Q@-P{%Y_isudTegOw!9I` zI=qW76pG39_WsuSto7M+G3>7SwPb%h4vkcq%wr%<;lmDtJir zUW(8#|EEowpFRvG21`R0-;IgPlm>Aqkw+#>h8hFDc?f2g`cz)zZH{tWEy0w|RY>_Y z`o0F#a>S6Vr@50Ig`I>%u&MkeYeRLQ0=H_H#AQPVrc`G=i#4TrfYEb*A@q96La)E$ zT)pwOH-%DY%(YX3Qi}9gk45YuV}{BsS4DY~oU`TDbWqbX(;NMrqp;;RQ2yBcwQLj6 z1={(y8fY~H^Rzsj#i4sg!)vwhrQf;!bKeqeT|n+pm^!%wlkgEdN|_tLbS924;6cmw z+cWNj=3aPT`loi) z2wxYWPFHZ&tus5r7ay%D-n@80-$Q^$^$cgM)o(I`kHyojv;5j`=J1ZSYs6w zHO6!f zoG2>QsBJFv)``)$~n63;Xd{cAVS#L}p9F^-L_ddj%FW*Xh{R5Y0Ex@R-OBI67b0 zSf8_5_F4Y^VbFNVM@H%k4z#l%f2-NO!Fp+^hWUKTQM&Svxil)t`pEHWuh`{qD=mdo zO-n>8bn5|?3!Inp3~e}F=^~KNmd|TyR+~*=BMO#{a!8`~sW)xej@@O*toL5{+cLmv*l96bRLy?b!RlE?|K@`^|W5jFJvn z7f{$&9kVVcVb3EQuBI&RYwlX@&xKDn&5XBzp3|yd$(tPHQ{geM1t3}C4F(HCrbIJL zEE6>4A_E30hEUeMk$%y>)YTBZn7aB|Y05D&>!anuPO=J|g++GZ@VmCWvx}*Tg#Jeb zz1y;bg>|2BvDIcvX zH}w*HCaoYNgQ|;(9xpWzsGq!7R-mYtupOBU2LjQ>R3}T@a2gvP5yTd|`%CKQgA5vm z+ZatdG?oF+!$u#sU&NLN$D6xNg#E(N9EID~4l0aN6C`v*OpNmc2 zRXh(`7$=rBhBGH1g5d+BD&td~oP*qJ_Kn;B+sHs)eD-hBW<_NI6Vj4^vY{B7&&ivy zS)y**ah{8HDRD#wkK(>gmVp_4`Rcq^KKn}4#J8S{z5LEw^SDwOVJ$U9E4Ug#TWXKD zV3ZJxOqA|VE^B3So?s2BVroPW!Y$3M8gqb!I%$abyE|&W@^mBN>wJ`YPfZ@-$J+XS z(+@FLq|=QD)Wn0cF54j^`G_!R5WX?mBKn*CqnKG%i|+Vq02ViUa|$m~q=7~Z7qm9E zSZZx%2k+foEj8NKr4E*=CS!eiOk_?dNu4#1rpk}vfiU9R-&*)m5h}VQ2TXI33qLTK zr(&d-+EchZhGd-E&;b!7MMxcL^Am39z@AJ#DSVf!MI*K10F{~o%k zCkCDK*A_o@82~XtOMz1HliS5?Q!t0FZa-c)Kog`XX4#j1nQyl*6{srU1)N;ch%UbA z2Zz?E2~hI(H>O6%gA`1(M3TwUvXe@+4WI)_7&xC?Kb`poM5+K^=Cpx6 z4!=4kbekqjjzjkk2@JxD3{&2)<&#r}sv)UR2znYBA7rtiVz;+$A-0AS0a$b?t7l$D zqnnOFw5M_Jq<8~e6wCW-~vehz0C?Y#zehFc!Tu`uic#?vm zkAb4K@wbo?RnZ~yL(~PXRDFF82mRtzBZ0U}v zTXuw;;n1daJBV)N_%k_$-JsmzSAhL8UMHOrH;1>buGY-#3SE1h{+}H^j4-R{&CwkBAaF8+t+>Aq@ zkq+05wNg4=b;rX>(H7klG!W6H#3M!vrVbpFyvo^I4FV4~;@REelV6Gg3rBFuy1WN) zvGi`x`6GZQ)#f)!G4CYvh+wY7~oEUnxOT`<)p*}sXAe(c_TyB16BHMA;&j}zN=XcD+qr@*7J(wf>XovuF#iaW}d@++u=FF=Zi<6^e zQ9}B}c7GDfBowrOXJtUS8Wm#eW$sjJ?wY;G+O}YjVQ$8JO`}P4@w4Wg@v_fB0-U3< zg5`t4^EQ*Hpz0uSfLwb9%uXi}A#$CbUP{`-(l9@QDp#&p)&WkOLXq3bx4xaeCiQr1 zm2~f^GDSIimIZ~cIcF!b?8Q%tzb#HZ1A1EA?`_iD1|E$zsXs(4;PX{J6vYr-3*pNV zd>O1~9!p`E(WyC(1U>#$tzSaxI;jpj5AOQDNzc zT663w$732h8KBhH<2)csND|fTNo!l&@lX;o%3r*y^25?u1i7<9iYN%587A7wH6;bT zP5RalBH*PO2lqxYcP7-|kXg!DcbX zX{5Z$Xnq*`xlezus5|iWcr^FX0x^=OZK*2T{4(1m ziXF)1Cfo{Xk8VBh<-X{AiElk^F0QH*Ka)?wH1WwY>thNZuCZ1o)n)IMKfiz4zO1CPE&KjSiG5x)?D4^kYc{JXeq?tjgbp!{MM@4z%W+JtU@1BpC@LE5z)JkQ1 zo#K^Dlw^VN!{IjzL7Cm& z7aP0-{N?nODUbbij-<}D)?a1bbfu?hFpjH!glfHDHQ-W0DCZSvC7@!)3YL|`sV_62 z$U7%=D;eqU{{=9)ltt&fYkm6^s%vQ#+aUA#a@$c>+X~2My%yQaJ%zN<%cay9j0vu} z)6`qVie%8x1z;u=Lh{y@?0|tm71E1@-xB}wiIl-H;ecYPghf=-tSWzFY>rQ<;zKlQxb@4upyB(=MAtee0<;V!8l&ICfi`%=46rQq2rf8o@!I!h#F1b$*a}S~1c6pxE zHcbL&$2nn%&m;^{@UPtuzlnX%w+q{#bO%WR*Ygb>RmTip9B1n-7SG=CR|`gcb? zqRNYlJhKYCHL6Emn>D)1fXKG>XBbq2cuqTWn>u{Yt1iPArZ0KbCp;{2n;2)s^-sgw*R<`w8@9i4dt1!v+$o*@l0BPD6@Vx6P9s5mg=awl(lzobeGN7NqNBVL z3#+6(@9E~6NwNFPORrBWjR7+26VOl{0Y~^{OgPemevXBkoXb|c_aJSprd$hr`mgR8{-n#}3E)wEeKQ)+@nh*QMurqj7KUSwV+TC4k*KXS^$KC=B z22tiFx{!rWQ8xJy|LhP%$nze_rn*}P8&}Ki+<~3P zjo`OQ6=&>CwzmsRmxaei(}DM}du5(faJ{;scg2x51d+!rIOgW#Ps3{6fn%sF^l3@Y z-x3*i{+u&#{=wg%hb!BUD$7%&_J;gPMQbx=l8ROqJ=21qN2W0@cd`7KS?c~ z&qb*DcX^qHY?&^>%bkmlo{Opzjd&WCcz)lu$F(-($Ss4^N!^$!BY0d)4kC}!c5Yo zZEMoOiqx8EdvJOBQqn|%E1x3e1@Clq%{1J?`*BaRlJy`aYV$(Md!{nO?rz5rQ+Mh1 z2LmwT(Vs0Mq6x#5^FdC4v{+Bw1oPo3o5OP&_LWU`P;Gle$>bi~EjWAKXizm2_n7-o zx=$Nf#l7P{yVW1DHdS~iJ+6`GEbf0{=XXV^ZK2g65{5iVM3#N<9L5qoYSkpy zz8_*=6cJbye(FO#yqD#o;r6CE&JC@Q@n|M+>O!7R5z($r+wvCG!9ECr-pVZ}MB?Q% z!gqNaq*)X9l303S5*XjOOJB9Unf&y`Ek|i zN#c~>9jFi(ptz9y5Z)f9KpS~SQAdk|=9w+}NY|{mM}06+a2}gF6Wk*)+q*myHIoGZ zSl~_3KtGy_{FuY6Puo)gwsdP{wrS`t$` zy)W`=6yf3;Z=^5dyPaAt*#&MsO~W|WIX_$sC#y*1^Z6*2fjhmocs6UBUXMZhenf@y z;}E`voIa&7B1yFt^>ehfTLTE_?YUwxv@1R<0U+CQ4v4UsxLA&BJIa3Zn&xN)DLhqd z@5^#F6|Eu}ZOOPX`FHhMI(Jc){t-P)kkk%owu-*w2JgC0P#i)foIhRkNWCx1AFs`k zwrJVF7Y5PaD-kXY;$MnBP+)TqA=N2cvf)VpmlH#LcQm^9M$)6LgoLwM1T(jj6@j#@ zKyIrhoGlZTc|FzZN(2th%!-`Pw!HP;Gw|lvBI5TTa^n8l$VeMIo%V)Vp4OEw;YR~l z5xN5fkBJR^YCX-)D+WI-HFT*8O7|L&?7i+aE0FRtZXI)yB*|gs-PY+*l!p}!gKOl1heM~nTL^(Y$ue#Ljo%3R_;^-ac;4od1)tMdcYX{dOagI``P>!szI z)*4FHE8(Y1iZ22m%{L=wMI)4{3qrK$0YSjXanMOboJ7W4J!)|h7PESe^-~~hl3WJU zfcOe*h&i{SF7vEUj(uE$Xx;*@WTyc`gN|}!aFWE`h5*$HBtBm$xRje?V^PtH;$v(B z4R70XQ%9ZQAW$>*smpQi7gT=2_obl5;)psbL=U76A5GoRz9rndIFQ0|-$3Fr*dTo4 zF@*fRXYzvkGwx`!M{7MAADGnF`)-VSty`< zwaiT)vXhtR;m|^%tbyp*$O13)N zz?cx;Le~W=H%gEXSU=^S&+yq_q5xeqxB(B~B0wrP3i$hJ!3`ZWXgncC%9q_uBE`M| zxm)+r#B3 zUMS%H+MOyI!6NvI(2EZzi{x7jkwvpJ+QpDm7BDZs5Uq;0r>Ox@#vlQj%aK{itnoY= zL6vPw$JDsBz=d#5Aj|f+jgM;<=gsu?n7%rjdR`$r#bZ~(I^}YQ`*r&Fb~srHiv*Cy zp{5oU4)bS5;1^@znbUHYvcXbgOXkJihlW1i(k(DavFn3x@^!&2=0@UtUCMp}$FjW| z6K>~8IG0j~xY(nUj4-!N>Ek>Q`x=KSzN2d)46r`DKxnE;n()Yq_Uu^`^d_k}Yis(m z9z!3Wg&U$@@M-#ErN(j}ot2hd)|#Kz0lf&}gSj4Z?(pdgy$~cTR}pQ zZULvLbV^GM1w^F;q@<*g?q-05(xFIqNe$`ln3B>p7%*V;fWa7Sj2FMZ_kGWK&i>pv z_x;@WC$8)IT+i)5NaV?_Z>?&jM*ashVQTNtx(wfrVyn+fT~mMS|7xC`!2Fmq)H0_3 zGt`BgiVT}|=o9t;&s%0bcUbv;Z7OB!SD%)=*bg|F|Er6+jpa&?h>09$*AcbHD~j*= zS0T55$*Spc;)f@nDq4^KHz3oYq0mpI(Z#V_Ddkzq)%vo|LilX~p*W_ zuD7GZA=Sglo``<>I@9lJ&N!B0ptLLK`wB6?p~GBLMi(?pr$LXd>mbhTUSQko7^qd` zv54q#yS;#oZiC3_;|E;_nYBrSbT6<6X`lF_@OuXJVcenQXSOnx4{&Kqu>gq^AFFNH z9rWv(4{|PV#qr1ZZv2k-F>t**w*Q4>ssD?Cp~aXzmtEL(mX!ZZ)~xJ|fwgb4E=4kB zk~p3mvoHK>_?V}!>(7&hBVY<_X4sbDZT_`tUy# zf{-B1^nK401WS@U2gaC@!_@P|*qcW~`(=x{H62pZ&pE!oTaBQ%Z}tSCmKv=`sX(2J zP<-j!2=);N%x~zY-NfS>Hu?JgELW)T@{?s~vZzp{qeL8E=E!h$qlJUS-mLBD<($!I z+bJ&-SHU-5(@BL&`s`AvgleW$Iheh%uyir_d#iPJ zN+p|Oj+dHaJuY8Z+nj#I+@&)iGBC6!s$x7o;x}n=_o|#Gb}mL_Bm_+t3D0p>X;uk( zKksc8nzXG+#AVCTJ?;27-$A;cWWkW&klh?$K25m$+&`9pxLGuBqM`k=#BwNJpk zyb|N>O3@D_76Fee_Wfe(__FJb9F(Cc!NBlh5PWU#1QCE zDR#uDc@hee8rcj1Y;HjYpep6_>Kvh$&ZpLwG_R6m};Bd+6 zN7b*=5pzXy8{JJq-I3|HiEfK-Z_JjZj63gfJm~6Qeh4)+Wj6})#K}~QmHQKb+M_zE z0Yh&)R`jPk_f(Ps)9k+PE`jZH$PCKD)bZcsMH54)=S2tXxq@Zn?j7G@q>NlyN>;Jt zI;czg5B>9P2a6yZVSS?;>^aK(qp_C+(}m!Y&}yWvgVBjT+T><_K``t5LF06om}#zS z(lz<2SXz6^Is1$US4U4O!{MI&tmEKWPn97sRJcPld(Mu%M;)4ur&{|{hu`F zYshPWR<4HBBeNT=u>yyNQ@YI~!Sz2s0%Z10A{q34KKjI<-Ke)x`j!9g`~eV5FPi)5 zqDRwZGiW1f^f~;pSekF-ldZhv7|x>O;&MhPJ=1mXh~1}Jk_ydr(jMw2aXnWbFCP8g+{$==NjW-T zL~k?w-7GSFhKX_15Rd%F8b@5ZBzY9RvF#+w)ZChj*QpZNx>V!Dk*Se%`yoP*w0_JvLT^A>3hX)ayEl9 zuMxRY>iqm<3v_Qt7mgzP1(LT~;!ta5R0|2EX0}*U>wm_+%>db*vw{o4S{gP0)9&v@zb z&HX1jcb=iQKfWyq`lmZsxflqPRQjqI_=IxgMs{L$e4OL$G6)@NDw--IaZP*o6HU*t zm!trE5@I$ydtv^CQ*s3oP%FfugXYN^ciqfeYdB+-Sbjf}P($W3}QDH!zHv6KuXEa&*!GQ$f%n7FUqP1j&lZnZBJ8s@@AM*j_%Rq=J;6&{A)7p zazlQzR*bsp#Wp1BQLjDeA>(<|d@r{;;^Ywrd3t%hM1I3%F3;H)X3dtYRhpNM*=gHs%WK|1y9iq@7cQJ ze$hPShWJD^C{8Ar$+G$Xz7;rVKL1yY(cM0nRfIooDUu|;JbLn{wpb@O1pNo$aK4ei zL30GD0jVETDH* z43QN9XDL!imjBs=bdx<<%Ud5sW-HLvYz-uT4_GeB(5$AdVxIZQq{V&O`Hn?2+b~=D ztP%yHoQluX@nk|N2s1ahj*m(@2i!IIDuV;13Mn_!#6 zLEKyvHSY-_`JcI3#hrExO#qDosFPzX)o@Vi392e*ld_?^X^Vg4g8+yiUafzR60efopCp8T5ostf&6^GR8C?=FUK z#q?B&<&N#XWaoPBp-F_Yy3$DWPX;%m>01`f4Nn6_4>1N>^;zjRhw{lS0?dIFqm^%v zNkZO7^TPMfn}xxwAeS2f?cWrCcsp)>J2{$at)RPOg%g^%)Vv0Ac~srn6VdJd296Eh z{;T!}5oA~+w*vGn9{)1lq-$onQs>rv_d!qeF0uLNIG6v~6KyjP)cDfYa5e3hyY~GN zqIK1{bx}|HghnSB?dMl8zNwn7Hvn-8_7!iH^BU^Ey|vh~X@l!zL5lfijj;5425&o)*JTf(Is>Ch zM&y$3Q#p#`G7`#e4z)=QEFAK>5Z$0I)@<(;Qx=@hQdcZ6!MUTAHgok?29DlDThWyB zncCC1K9}FA#v}=lf$ybDz!-Ms19O;F*}cN#*0sSB+WiA5;qSVr0wN6tl$S)rv6|ca z)lZS4#=gh3RO@EvlFEJEC#{6!~l{%uL^1(G=_%M7aH@Rq9Z^ z2?sR!@=9(rDsR@F5Nka7)G5R$Lm@@i7w2VtC4U8#Koy#Sy*CHh4CMdfFpGqv5DgfK zXr3OXoHWKt|7D0WsBm`?#PL*{cBaY|OKqb&{1!fZrei}PIcFxX)wCxl(`_8QpyXjP zdi&LS?e*9u-H0YQ-HJHs<0xxaeMsnQYy`aNsf1ay31HK3HbV#*W~;)lk}lVIv^zuH zKwVCo1!$`COOCr-QTe{ZadB~o)LHG02ioXuhwb2%AL8e_SmTY9U?YAhsiVAA#v*y1 z-GJ8l0oS$t$QZY!1w%{|GQLDR*iyc~M{mE*%pinwS}Mv<&km`1@! z5jgxPsKp<2;ZuS50ho_xsr#om*CsGx-BYeyv{gMGN@ppHe%vbaJ}|;;V{di%>cG7TENso5&uo-;cD3u~kMNnoRWV=4!pM32qcMgIgqUG(i8j=4GbU^Gu5B*G1z$ zbIjawVXjO=99i(r9B!fea(D;J$bwaFGv-Dy82o>xjtbRu@A98={N{PMUx`W5^?EuU z^`=H)@xybsui^7=P3b>A_SV*wFy^zr6uLIBm2h;78`(&CmWw;;+(KtkiR4~mq(t4W zwZFd!sI8NKB0w;SR;+ajE;5!B_wv)GYTTq7ellAnZ1X-!`XkV9?F%O5`ii*{i9;8E zG7?BJkS+Z2+PO>f_T^80HjZ_Mbf-W##*Su!Z=GqT)wB7x8WAPh=MC$N#7lz1 z-~jI%zA_gsJx|!W==0A>c7Nf{P5*^^$0&GG_&O#0dJ}H409OG|xoSK&c2bK?o>cy_TIWssBVIFh;e^TWe*CZy{TBh42`XDJ9Lz-Qb zqvh=E2Ln~1w8UgSpwXWT8gl;3REe$f3?2i_@0`Uab6@fel+R;*62yVn!xvjBFtC_y zkT1Z)VCJJva!o3wp1r|2>o47s5vXHO_6jF~n&ca0cV;zBh(O(R)peCx zN>8B|cTY2Vbo|H` zqnr;R`Ftu!mA|txB8}q$Ia5a!R>1#+^)h!idXJp5hBX=r$>n3OB->;sJzx5+AOBob z)#FG0JdAB&C|!@i$618!#WQBL{mO7T{<}EosrbpJd(PGL{^#c;L$UiDQ%6hdvr--r zEg8bEue8Rkm1B<%dnNcv5b4E=vCAe-;ai98&p7|1$5ZbNb<#6 zNl@8xji!3o8tc>+JX|1cG&a7o)!_oLy^JsX^9_QO zTfKoXLVpc*E^^i~EI1EXCFh`3d82f0y^masMC{-hwK=_`9)h20?qcxvL7#ZK#UvEF zI6nqUEKpFlx7eImkeRb=F%tf^MzO327>QP*OT?jI3x4IV#KnqD#0+5YE_d`f(q1_-Jve>0xo z9{*nCi91hg5Q7^9`1AKS??2C&*gJ{71IFb7xGaf20-UjgKW@KEvOZld4ZeW*d!UKV zwytdDEWy`nyr}NS)$j%hP{!0;^!d8W{&uY3q~}Jy;>dOq(Yo6EOh?j5M6djxv>~pP zbT`X`B}R`lfuQYU0U~(n@dsiKVlSn!Qtt=S*toEF9lq@>($GdB5Ez5MU$<>eYQ~b+ z=yJZUwitIpea4}~nyk_K@gS=Td>Hd=iKQXYeSr;Rw8giVBU<4nb!l$VL{nj1{517* zFs?2UYq<(TW*7_ltSoNLaZQhbui5(T19d=>_>Z+xw7al%}Jb4fFK?0sj!)qBYTq9>-I!# z@jEtZXHx33O)77AXQOq!4ilOxJS`rRy(>`*8u>pmQQm%+rRPYYm z#N7qzAfERr_+q1oxV?{L)O zXMb8YzWL!wG%Bm2PdU?mtY`j6D@}S6!vd#F7x$X@CZea(1esHlqO(NlD~@kAI(tz7 zckI*}m~PWhhyEZ!~9Z0a;F{ zP#@%0r{5;+FxDWS0&9J3r%!bt$GCZ6Temu9THnG*1Ys>)6A$Y`!7tTw)B zVD09$Pll4Pemn`#0RrBisDOXQV86xB|G7jEYTickoQ}KYb$_uO8y|Q!&mN6!HkJ0O zUvl!rCt|+Lf!(jOT5ccQ47@U%OR=$mCTX^8CinsHL1`n+nn&31f#;Nweys1gRYGf` zJ>CZXP1Uxh`-&W^)t0hZ&T0PaY?IpiVs-TBiw1r98(-tKlf@BANg|@ARmBaBxkQ-` z0ezf0K|^Bt_2VXiy2oL#s!g1}G0(Ny^=*=3?-YjHG}~c!_G2oHi3|R6;RNt%9u*bM z%+T9Xg1#!J17>JOeO*yq@iN^RXxCwz-CE%rEaS5}LE~p6Dhf?Xk~qQbVG-Dx00Y^-@F8z(bQQS6L&a^?~|#^YSl2P_RO^>-;yU@7Kr~; zQF5S}Q(J+BLeSlDU>-6>v;BKuM}%@3Qfx3^!?OGfWq_Y@Hr#%jFu~c?XT&%mMsQ}q zVBwp~_`Qxk`9{vu%Kn(qE6xo~)z0ghIK4h_RhJa!55|$;)JM99LosOALZ4ysncQpp z6zr18UX|t^;Gj8l1ASzOZg4<&zTdga^UfNKjvDTfXJpom>FEYsbzOG zW(q5?iR`e#6CU|FsDcJ|^_^iDt4@YTgm>EW zJt!(B@k22oq^Ifxs=MCCqK#$@;YKz-xCoeax5m|lXh?!Yx>Mt21&+>nQ~G?hLx%~v z+|Q-kja7ud+xL`_dMVvW>u^?Xe=n>;(KHl|mi`K_^wG?aciz2X67ZLwZSn~S9L0=V zbfP2JC|~+|ttK8#zErh27L?YP5eo0_n zqkE%zo3!o#xMVv~0)5LpSSn8#_TrhO?V$}D{6*ecZiCH3@~>$5v}#4iVDXheZXwDw zC#Tgh;N6-a4C>>=>c`L2e3cM3iGFPU=cXn<^965-(-$|L9se7B>9()5!G*1J+n_f) z;$23Y_#>*|H=m`hg+r!se|i%}uOn|}ss#;0uO)0E?4uVi0mnND0$c|!jRju1o^UA6b|>Ck-hKbHqq+rI zFhDi<;+Vh#;}NU}g@}uNpwGc%UGU0V?(^BQNy&~Fc@3bemd9FMdxE|RWx4_`(oBJnV(fxf_*yh1Y9oz(k#2RyCJ zIafL;ci12;_H=jr3+35<&-sS_eWzRxbIy81<1tS+NH;}r_7O+_U;bzmKo>6pJ|UvBgOJ_YR&82&&;;ckO7ekJ?>+aH3Fv}h-WJ}>%9eP zH)kETJeN?Ad(R*GKuE7oR3}EUHsQT>1@NPGFl&pvr|$)= zK+6+!g~~mJ*SbKks2OYagy%N_dE4#IQ2@q)Ydr0U26sEBAGkKNP+mcmD{=V~Qqs|! z9#&OVwiTp#fMU*dqL9kn&*Bl~?ChFWa_vSn9@#!10ajgC*_)53DfTf48d=+xuXe9K z-u8K)uQ_!E#gtfg7PXok2qm(P$c=19csWR#?mC=u6L35vFj375Ma*QBlRYD`_>J`M zv65jUP^QsYXO&m4$|@M1%d~q25c?ySo z>_PXx%(h66J9{^it0gxV>|$R*A8ySTZ)&|bhJ0sqLbJ{?KboFH4>$LjiF5yG#XeYA zMg768<5-5z7rG?ioe?oS2Qq@eui!`*LpZqYE*EK|JHG5lebLoo=a}oObWdXP1&LWE z*XPMTECPWn8~m}T#}T^1q4y0zEcAixiM3!iIrekSRqfKCvBa8Imh-V|SVb^fX^)(4 zSBdi>A8HO@>3zU0d*TCeZwoMQz&nXlX?{el%12qqYIe%CbpRK-^xe&d1Vt)P zl4#|YmygLujtk-VAA0=F%LoPb?#uU%GqBwW`U}v`8?64(Sy!A60j^@O@+A6?x>}xI zZ|%APW{gUVC2luC{;-92`n~z%BYwjhCUhWF)50DihW3q;C)EzgH9_Z_z6}XQ1}y(& z14aURg^14#?3vte_0HafB#6-&tQC$*pl@j!k;TRJGG?ZMybqrF3?EcLRbarO4#t<_ zCCjf)J&=A7(mM6bz2$En5;t4#&TnyE7Gc-*@9;|lUuN3FLjT;Au6TGW%{%Y1aomaEW+2- zX|<c|@Un z7zLK42c%N(F`SE=*XvDkq+J_1@yemF#*VV3#ww5uP-gkND%XQB%jk_$Yc?xr2*W0@ z;c-dEO%?l9p^fxi<68MPXAvgf4)TeoXE5RI?qj+S{G|y#&s=N@?dG?J-L6aNlCGnZ?G@Z%85b7@mqGu=efSfW;L`7+;1p?& zUkwQc^-ATvT2d8a-lUX%DD>W?BOn{Q*1*@Bcuy(m!ec9^1VQLSK3v;PWqG{Lu)Id~ zmN@twEyywEnvwn2o9xcYgxdu|=ig-auD-#qP7P}A_*WVXIQV^1ELoGGPGVXmlJ9I7 zr(Q`5bU7rt?%0nDy=M_FdABf6ED*FBU&?RyJutKUrr5Vk(<#h;H5z@m8WgE{3krUe z=_jBU{B22<-b*k4X7h5nT#*qdA&4eR$!g&S1*kUtS{gKnv6k6w)GXiC58}m#BdR?o z$nHyJT0HJ7C4KT45|97P{jv*xI`uiM#UO6|XRg|7BFXAh zTzm=>OYpvnytAe#y!`258*sGQGGTE*UEaplXFgx1#l+PpqgRZY;K%xp*~NZXk9Osv zg2=Bzs_~c^k_V&LrsDBjgPS+XjBMZ#yW4C0_ca;YY9D?4iFJQ8U4+j^Er0we#EcIY zvrXn@{fO)G*+TQW$ID+d%`=xID1Pl6cdf==Qn8@((}%Zi>PC#HF0bP`wa1oJ`OO_!2>59|J8mUgkFujkZ) z_`?eOIR(s=zm1`5KMuldcEf)z(gjyM(x?j_o1M-PM)h8J-!3UpZ!O(eDT_H&(5#nu zYWl9MoYO-_KKF}}qMGZM;q0=h;H7Y_v6~2(t#FGxMvKf)%#@&eR=5Du^9B5->X!>& zAie1eDXqW(hL?1fYfZ(ooU`c}wMY3>FitYDMp&ab;xMCuam$5X#2k71Ffoj{B?v<0 ziCvWt4M^BQUe1V_2>($L$O#~=D6PR>aZb0Iw_Vav`>g&(tzCb#WPSY)R3H{uLl9aR@9N~Ai0nJNk zQwIy&)5U@UBihAePyQATl*<98jQbp$ju z?;&2r?IR)&KO98f1AEKBJ4h0rKEFh3slvb{cA;!_n&)9 z#ByypdS^=LltVTk;|}sc2UCU18-=J&Srbeq(D%=AyfB+6B0)5Knex=UuYr1CYn;q? z9@oJIkYk5Q6y)!39t2n@py4|5;DaxNBCpmN2%e5wL^LwI$wS1Q>6`Gd2K?{e%lI=01A2Id?&CYdid&X_>P z(=cO(ItbWoz4AC;x|n5ekGLR8GjQv$XX2Q;DF4xU=mL{aYX>4u?=kIdnxFIpZUf`v zP4C!VFltF_-80XuB%5Th=6tQl4xCxFVr`@Sb(ednbdWYWfZn$K8FCy9oudHk>dRE) z{*WJT1zQ{t9JDi|4(3T1C85y_UN)LGzIHouj*hd6_5*a-H)Q1raC!9a_FEmf`nR_* zy;&AnLZCn&pOfQ@7KFr?`<9LD#r~4r`L<^1XeGdzox+==P)%DAF+RKRBCvMB zAS-O|>AIopXmhUC=lta^p#2hb zzE1h9x*P+vegkMJUgBLK@aR?y#ZCe(CZ+X$D9S$&=y z)2H1~(dopSFzqiM$_*91`%Nb5mf{O)JvWf4?}Ug@?4(8PfEK8R0?cdsa!830?Z=lk z+DwZ4nbZ*Vt{Cm18WVb&$}+kafTWH2?Fi8*{)DJ!`K0 z?5?wstAv-8AE;+J^4Ab!%gY@*MMpGo9N5>1B{~B`$bsF z-;GRM{HT#@bnG*6#BB>?BItIdo(Nf`4|6%hE4uEn%Irt0+o>J<9R>JTz9;DTP$_X< zL`0+d2n*W^d@p!Ygv8x>@*$>oi+9&8^)N_V*XsZJ>8}P&0Kd?_XL&2N-MR54V3U-9*cE-Fdtc!}$8=E6&B}5Yq2Sw?Muuv;#~6 z+1w!+-;3GQewpW1ku7Bgl3VN^roZ^wi=+eGcVg09cToVI&-fjp%rX%x*a_*wHOLiG zRc9^9n*Ljv7Ma9s`2NCW2$Kd1)hpt5Oh8%uO}q3K45AaFxSj^Y<Kxf(V4=f4GjiOuj0a_T=PkHOfWd%$3D-nv=uIN$gwPAePmev%FIPF5Qh@R7<#ohA%p$_14AN$XFUHJL*=_lJ#>66 zW;J2Vz4BhkQAKHzknf4>AUi-o+DF`sx=@Rb8Soy{tMVFb83`zeVVb6Lr zTcWPDa7*(ypBGSeswAaGKvwkzTP|PHReJs&2j2Sn?RZKfGGA?zHqoF8is&@5F*P&u zac<0i9&EesyrY}_{hI=K(If`=R;59V7{~5^r_?$KK8Dl;+vVSvmJ@S2^GE3A`r)kM zz^IvFWFAy6`}oV%=+)(>M$eVuwGg7wOk%J1sfR@-82UgoXk5;|_DZNbsUPCHGZeZe z{F#KiU2cv+^tx_!?~pUqY;b$1hr96~Bwn9*qfYwwI| z>keDfuZFi~yW5)=n`?T4{1#=;!-mlwVZ^!%PIjF~Q611vrT2XFVVHu5Pj>bE?e7Eg^4x@XA_=X%DRCWU||F6JguJ{Y0AbLs|#~ff(ed zsHsBqHA;N(4Dio?l#bbeU#RH$N%`3vS>$%s{e6}B_UXf#4`t(llB3u|Ko-VW(yQ3n zpEUH_Sf(CD$u12_?GAE#No22@yvI9O<%&r+_1jrsR>(Pa3U?omV2EWPqF4` zyVaV`!J#6>O>$Dot028IYF*1i#=iefRz!Isa`-r7D4HX2DJ>1PGLd_;4(EdAbvX^} z8d>*_*n@MOj{Wt!&KJW=P=TiE+NC+y&B-G6HnH1VN1|%|N;<}AV;PV`6h}f|p{A+q zWD;mcCj_t>ks|z~Df`)U(4}G}`rL{5mCn#Xo^lCdB|lk9Qw;JIsHFRa<(z z)s^sg>+O4e_RUr;{AX3y@Qfr>9JP*MnkcQ>X%HP;(+FOev-UVtO7(J47V_@zi?`_Y zXrfkOgQZD(^~7;cB`Mx1th#B}5+1KA>tgbr<$u2xt7DK2jLq;Rdz|o+=cfoZ#F88} zFuQwPW~=qmbw%@R&x|Fv($(F%mduRD#?MlxXxXR23(*-|&mz=*dIV>M(v3TMYWC<= zZ}PObu5*?7oCC_;D);%Ob=uv&EKf>?!4}wpeBnm3S^n#ShvWcOB9dHFLqo(EBzl*u zGUJ&Wj*vv*r)hnvj9pi#vkWBK&Aq*$-D%XXaG7M>Xk)hOrjRRqw~1R=B-dk&JaCsC z2@e$=4!2;nU~bYiV!67))QFU@XxmxU#ljp^`KDD| zO-NL-myw3ac=Z7Po&vsgN#oU}7<_o~A)3+oM@?Ch<1)i7<#n#pYb7IT&G#x1tGFh_ zkqSr(1+#MaFh>z6=U?AP3?9xbx405!*KiGA-+e*Lw)QK zPx=RQYjxv^I-Sqz0vA-TqRUQIHn3yRWsuB}M5g2%yx^_Jflog2hPgXDmG zUx4kKl*X-sjp`c51Ps~-1Waow^chxc^iND$TF0tP^pO&d&@~C?!Sct0E)}u+;=Iz| z-E=(>kMMtkme9l-6;PsFwu1qHMfJ-pl%Qd3TC&gVyX}~!m+(H$Q%*k1 zK`9Wyn)LCRyckiLnYp;oUUJZS*Fe6I!*|~7kbe8(E&{P&nr#jOjM3wy#W#+&rtL?1 z;jIkk)^hiPSH^o_$n*6Oj%)Z6^f$MF)$z**zuQ;hsrH5Biz?$3==%Ww&kG=*_lygO zJ9gP(zC}WVIjF=c3f93R4Rx5tmE@%zf4%en$Hq{Hg2L=C6_s!Y8z>tfSlYc*H9_=s5X~W2*Xb^6YRk2J)1UA) zYkfH+$o)*4bk_XvteSO~a^B}-?V|k^+f^<|%*4LGxC+(v(<1G4pe8Y=Djq<@YGu5`OGkfAvb zJ7fCrA#7$DcI}h`CrVG_WEc6}U)~VYclYHKxvCj60;$Yz?r)I<=pc{)Mq=h3yAO)? z3eg7I$qU6TA#%&wsWj8|${h)4D^jj-4rq?QJ9nO?M?#J~zVn@BPHU*_xth&FXwKXu zM@@u_ZJ>MZ;N2&IL_r*QC?HbtPPOXNd=>li4XCITsF`mrvBd#WUe)At>0-Vl#n}?0 zpiKR{`SFp1XVk2qZ*2?h?`va2s{-ho!#c81CAR^knhlTn7lV4#3!KwBx*{(*27gU; zN$f>Z?LoGF9A;;C#ZfF(L3ZkcF?)PA^d%|)>Hdz()of@5<>H^Xg~DJ*J@;X9?wM8j zpK6*TJ=PDYQ{l2`?wohvG?Ynjs5X7kBzc;9a-cpQN_(Hm(yBOhMxVmJ4KlsdzLWj3zGbnov3a z-bD9>4&oQoY-A~kyay*1G^FI2V-+L$@pzZ+N8QA1VU>_ay(lSHUvR!W3i{(xKocl& zFp%=7R^q1aT2@ymXc+z)pv9m><1ezaizbc3xYpIa=ehw?Q>N?v6C#+w{+7_%CCT;3y0nioj+)!+%uLx+XvQd@kc4EaF40Ln*YVU|1g$Ly- z`b7OD1M?Sjf3}M@l4ykeTHJnFIsJMZJI#E4jKasjGEdHP)R&CdP~)pURTSPy#ih+3 z?0KSZ#>lQ0Ki~%IgBdhnqpkLA`7TQuA_yXLe_e4gZ==R_#^72(Us#oqx(AuvT(RE8K=pTgt?+JaN^ib#nKzavNw=x;_YBnJ9Wdwac4T*X> zeMFl2=NUE5-n1y95d={xY#6w>YYCcjw?yAx1%YO6$2V2gIBUJlXQ1MvLd<{#>wW+(VJw>j{X17y?$g+buX$nTgsZpJT8pqy&LF3^E7ekbyW)2-d+_B-xXxS$)8VOzQH4)f?nnPAtG;Z1hm zab!nlPqp)4(yIx}(v*zElY*4pX#cTmdAb$q_Y-q^x{4u1{KWSv65J%6A@MA$_;UzJ zb0+>I8hfTb>}CyPCuPyBIfhCui19r=tvZf)-H3>QbRQ8MkXYjwVO427bj zC8ht*_y6Mi<%q+|zc>4k!1|HvIh95rn4i63>cNZEG!$OiGjXZxqDG9iOlM0R`7^`f zPQh=nDyDu~Ati?ozfU#s=ZO<@)e8P@{k821s!@X9nWi*yu_twhq5v3qQ`zzOr(oRH zX}yk0S_fuQLUOOC;V@riuBCL#vEN0Fr8V#RdtTx5!e-Q7s=4>J56e0Z*UGe zrF?6{QalG3({a>fkB*X`>~xdw#0Hu?bpD6XQP31hFb$rvF;8CBClw*aiGRK?_q|y^ z!!<4St^a>TYv~eS9~|j)PcisT+Hqji?6i*FeZ4qxD;Bb=Ta9V07bHuGGJd-*{Jl&N za4*3F;o=Us+Gjg&g@0+S8)w@@!z6=S54W`%4wsShqp^Gi5 z+G%z;Vx5a)HE;fKG^lvbJ4HE8#e8Y&3IR^t~i&7f125S?lTv( z2W3A8dyNZpA2{{mk5$EAHr7ag-8w5062KzQxqYR`c_ily+Ot7 zsE;uRMwhSr=|g2hac$gYC0YM^<^TIA=+oY;?0M@8QY|6BwSKV0Sap^AIHFhh^T%ku zuUZ?#UN#z>?uD<(M<-bu{Z4k`4)4zE0u9$GBV6T9#AVw0+%6A6*n95ul@U>0%Ixvj z%E{-CkJnW_a+cN~1ltz|oJPg{pz=?WN?gZfqJMl<@gMNZczz}T$8~%>J1|sC|I^R8 z)tCH^+3&+0u~WC2ROjtXqg${XLvWJL{DpKU*6@;nvO_*95{t( zItqKtNkbSW-I_Z_I}J>-Lj*MY*c)UVn?69z8V-Dtp|uSw9j7PWbxdZ9hm5jaGov>N zUEo741(POOAzUGuKZ0Z630dNsemiZzvnU<=Ttmx<3IVPW1NwfhM#kBU`NdCmvFfXR z+O%j3dcv9-4BGAi@t_4v{?(^$hMW}M%$qvg8hD-2@&8|dByBSSTWu6`DE~LkL{CGX z`V*%&H;6m_^U+BGZ(I+XHW9sx-|=Q&MxdX)6<;0Rt#pb*-gC z@XON72DC)rjM3xjlDLWJ+;CG0?&MbqIP6oyiKhpr#Zo|R?4++(2OP3WyJ$raF7G~f z7jX^CmcF+{)w$)#M}LfoKuZX+Qe!n=E=mdL@uMD}%m2pK6hRzy{eLR_ixZe4hoS$) z$$!6pneM)0B8rRH@pIcmu!Pf{YI4|h^u7((GL~*YmlOpCT#71-AmA4e?y<-6rsHtnyRU`K-cEH{P z#lT5gCHqxY$_m4(wHIQPYrzJ1^kb{(4^HblQ{^dIB>BRlfv(39;1P8-B^tkQawIbQ z6@dT@0=`&z*gN}&Hi5y_!kFkX+v7`S46z%i{hh%`A+^1 zdv6&O*S58d5?q2?6C4uUNr2!%LV~+nLkMoc-641g?gWBc6N0-nE(rk|58AlXINT=Z zBzvE;-@5;9-TLZ#tJaTI-Aks7@r-8Vs+*85Ddh#G(yfL?;vKGmi0eZp`=I`-27Xt zUuER5NK9wFmBBE2)wa0#uTT6+%1?Vl%Z*$quKHscITdf}hk~$w+K{gTdSk zCDg`Jp+WYQx%G$o1LrxENIu3ZXtUebH6xv>`I}WgLx<9-9}RcH4GZ%Jvu5E+JDeKE zI;^-|Pkln0IafA(LZ%`dx$SdAl|{&w5uR*Z(+HdBk|GJR=j(x+0%oRhg+F}g-(9;W{IOtz5%#(b7pr{ zEvJ%7U#dm|Pq1%#TQ>=yfVncJ`30G}k&lOr!w_Md92I4(yf94qoWu!Cug+%%a4PFc z2q9&^(0IP9u^Xeh5W4?xrL8nRj9a+-NqSr+h2GLHLos5^X+<`nG4jC^WXq3-sqa4gv|AGTvP1 zE+*p=U!MBR0F)K)K1XBI{K#veRw`Klug|7vHHy^aP$S4J;fOgix}9ne&iBW=h0{Cw z-A!4YZTRu=i1UaI0<#sE)os}_)Z}UHKbey0$&kLaVT{lb?2*+ZKG(IiAmo1y`ix}8 zlHjkY$$An#^%c(iX5@2F*{?4QN4OWs$HgC}IsKe?CB%CeIokKqDA1Kbrw1Muv><%I zx!#PB&&1OprD0}*k(!g*95s-vMxF|iJ6%Vi7nbC;?t($u4#=J6-3s<&y^8j1*l|Q1 zKus|7%6HXN+AF?iy=wx*?noMBn(us+_(-<>RX=dsD))ny>j6Vn=f;89WP?|jtR|Bg zoo{~PtyBwNg34IZ9eXLBb!T4GF6~X!?^_Gbi+E)=9Ti^3#O<06HHT%34XW51y1(Mt z={>VQ#g;~bT$6q4HP7%S$0Av|Flp31I{sPW!1k?H72{Qz<`!?I2RwuFw*jT5{}5y` zAf6+A_;&fdU_=BbzI*FJfg&yHleHdGmh2*aDXteiU_&eJ$;oo;JuIU%c-%6Md#{XF zW>&+W%=GB%;gUcGyyUbSl&E|HK- zGhi`N%*eKr4@clqb#=)IP)^R>JflFtKepR8D(;3SwN;8kv5I=qtDuXeiWIyfa#rrt zRu62@yOpK1GRDJhL$^^ijmv zn$qj+0Od2SaV58l?n+d$tFs?% z@U79AqSlO@_JJS0jD-QT{x#+K?FMF^q#m;%gp3FYaIW;3Ors}-&T)yq<)TkECX zukemNrpn-Y-k9B7)(ERm4Vc#}60}ya5W&xZ}C< z*)Z=shMXCrb)EBPZR$dx!1E>bZ&Cr5o+D=9SKPq^af$GUqQ>a}TI(%+T<81#Zk_9tC^N%w&#(=`t&cE^uKFK3 zMCuQXQr1TncUoQ6B-|!qNg7NIu^c^jl-*W=l5?O>H<$*LC~UTOKIbiF(86S+={>63 zGt}!)*UWp0+TQ}i#)8@(uLQl`e~I)SkKO0hsOdV9t2@`KC3;A zS&eGkAknpkTdD78@`!k4Q&)_yi6Ck!xv!|n~#`;P%PWS z^0i@zJD&~Pju;o!DFt$#!wu37CO{f$1t|yJ7!v}i1D6P5_nbhTUxhH4D?+B#wo=TwLx;{^JQ> zg3=E~ytKaF31LDt6|NpZ@1d%`NY!&X`|`uh-kJ``g61s~xU5A(cb?BX(|vSEV{bvH zw0yq4N!H@FMSWGjB?Eb{p;+w#~ssG{)n?Jo7GF1b}vk0nX>w1{S8n$GZZx zKOS*fC9))nLtE5X5_8ts?^n?I7!(m!m=m@jLf~1K$+G#uzHwofC`vjit8jUetvg}! z4bTZFZy$7n0@DR(@%dp~u{{iXmDC#9%h@T4BScN&OA(TxBw3|{x)Q-A%}5xs9nFO~ zk=+h4vXSYyW(CBmw<6@#$83u9=~KI5)>3AAy%5-ihq2#_V<1kmjVM{7BQj%B@7vQ2 z6Op3mz6Wx=aQMVTqL!Ab9QcZt`)%$>UAT3S;gD)@$khih*cmJFoB82!IxRz8ecK{< zAH`?JEZORM^6O+Y#kWzLbC9pySUZc*d>!>($V^-90Gx8`8yLVhkf%_@U8rwZDA(43 z+7j2z&Rs@F`$cYFt*wq}0krZiX7T!HG3`l$)tuv4Gx8eL-W0^!XQla-h^G4eG;jTs zql{k#eM2OK=Zg%heUn;j{0|_yDsr+1Rc9yF{XC4&d4d(6hzCRpH)> zz$#BI@q(zNtceVUY4bQL_xKpZq#oxY7N}AN3yKbKd{W1y(*5$&fZ(lRVj)5s72gA} zT2y5iw@S|5!zQdGk`jo^9h%UCmq7g#LDRhu?)z*|8pIxq6lH~z)H7~dqBT+!JSj5i zM~M{D3B}Jjb9R2H)edYkoL@DLbzYlZF<}lP2^m5z?bwm2mRa8DXGV!yI|e+NX-2tz zCT(H5f;mqb(bXKkk9JeF2;kh2uDqTEQ_E08?bV=)DPr^Z9ZHEWB&pX;s!WiNtZuHq zMZD;stx&wOBm;=OBxw&>!JXz}NYJxF*Ry3wL}(3Eg)@x%*nuM{Z!#3Uwu3MJR|>LOaNQr%_kt4_vE zLYTHU=F%}rP41t^r07o&mKI6Jj5*v~SZU^wAW=1f^{iGZ*Bx_r6Y8T-pp%bLs*YTW zYYWL>u2mue49Kru#0-VuZaQ{55|fL~rj40;JvxhL8ahAHPC8lai>xtBc-aP^6Jz_q zMWlFmdOQ^St*$@0w^lO-VTZTi^Ieg+$`X`X0^%qLh4yyU33KU4PLG{b9n)*WzW%FSpE$Z?Uze+R+5wDF`!Zg2xY#h>YCd}Y$ zmCs@2MU~g`VVDIlWu%ln`y!e|YBP6;IE?lB_-T*y{)hV5H%9^|udDjMe|4{E_PA`7 zNJ6S9^Ls-N?lgQ)3R*VcobMQY_%;INw?qZD%M73@&+=LU)&Op3a1LIBU z--V=)nl04y#s{A~a*&s&zXo$$e^u+NM4#=%4mlPCC=qkWQ>J-~dU?EAV2`U1e0=8HYH<5)J@X_!qDMfMSRqH|{X`8CZ$sztj|09f-tAj=Ec2T% z>D!v5{v$88CvdxhN#j7W?k*DL=SUrJ$kHV2a`qaaC4Cl{NKjg|chbGs%a`n~VN%co z+n@;9^@QL-UTIfps8?jXd=Y8$(Q|n&zJvL`4(<$HDw-(d5pJz~Eef}EbdMmRC9F!` z5gZGp*^}o2jy@%z@w z*G;s-rH5gNM*77zw4G1*&ANCHA>Z(*n0Xt1)gGI;)3Ld*7{eR35L&uX--B$9zFux9 zG?qv6LeYlJmo!7*?CX1Mr&`qL_5BsT$88os zy<)3oLs37Getl3Qu}9;9AIk3A+uyByA*IuYz;;-bTf_@YI*Pv9gb{frb<&W9J~^rJ zZ3yesUeCm$8s<-U^1%CHQs@{-Y+RWPIj1!iR4K0SW!W*@*1~LVTNg~uP4*uhADq$& zpJcx~IhMI|9I=RhQ_dRoM9MV#WFKGR$&f<&5#9!OWq>{dKW}uq;Q-lLQ$H_*zTe5$ zmNUhv)6i=bqghkP;W(pyT)*Zp7%HAejMjWdGLluxMO6d~-L!~;H#*qoUe?=@qWrDS5U2a}oNTIYO)|pQo#0i7ayFVhIZv7g>U5bdnlzaARmh zGo~D?lQO`Zft{$-%7&SXss?$cz9E4UABDf22lI(=_KT%}M#9y7MJQ_D?J==R0E3^=#3 zlE=&N?%?>Q8|J(1vrMn6YyK%F-5FX-J`+~T&&b}-dC6$&8KoZH^`kZ&!+9z|C33ve z`k1FVMX95XJ=!o4B34*yG%<}=&Tsw>d#Ze{YNOV?;8~-zWMoy-FfeUUv4YBtf&?() zI6bF>H`i-zW^*lI&zXmQ#7 z*fAJ)GM8}6CtNw2aaugp<6dKK{UOuJ)^_>o8_CUPJFlNkdQALi@8kx$&rCqpDJl9D zVq|YCgiNm+)>>mvuKUfwtUBY@%aOc2@2!0g9vYG|kzJQu*hQ0;Dy)noUo z2&}BQLZp=?-A81>-M*6%0oNq%V_n7~cC35e0+-WPuJt2gD;c;qtPa1vh8Srb%Gw6K zquo)3Sc0B|^o)ay)r`?PnI87GGx_#*QiZy>c;Zk-1&SbDkw%fqcp}+w0yuL|EVTvM zyGMkK-8x&zj?PSla9}SXen3KCh|$<-cY|q)EL@;{;{8eah@|C-%%1LrWhXCh4DI)N za^`5mvxc8jwE@w{FLOQU$~HQPR`jTF!*Q`Nmw9na)f&G9C>iQ{m+ap?!&5RSRq--U$n{aVag}`WGCF*0Wsi6ZIhqj*N9;NyuVkA=uZX?) zlt&`HGXgl{tj@dn?PKPZInC#B#xt?o*_k}H# zZ_f2KPJPe2g=pgY4_kx6fMvq<#~4UVi3gL{w?mC6Mr><&f^b71Dr1cLRvqPvV8`DW z0*P2>th#x!QwUOOUqETXn4H+ObZ(ENc63Sf;Zf0Ed~k&qb!N#fU#-%v7F-#8NM!OT z_Thd&RYqW~avAK;_@;`#dm0)f882NB!DUKO=#fekOcW{4Lo4|JMT}}7gw30lV7fhK zpDjpsM^7aoyEd6r*Eczq>DZQy0ajj#$6#bX@45KqXPGqSijtHWJc!GvfUfI(-gBWt z-)22M=BG-%4i?1fxd`E7U{bL##dfk0_Oo7F(!P_zr%>ss@E*b&8}M$4roNgajg%-< zIdPiu#hrWVZ_UIORH}iErMa18fjl5 z%5S}OJv%dQXbZ=KOXLSxhC&a(qeMSRmyifKfv{wdINn zM_Te7OAX;tPm1rpYrTI-M8fX-kwm5-ddGqkcWxca=B*)Cs-)_^5(YSqhNWfBgm{+m z!I8R&TIn$&AO1(t`ZH-!OUgmQB{1amS*uZzSR|pBiPyQiO5ZCW%MiqF#gj0ts86X* z19nYctNX3wH~p-d zTkN%)nY;J65rqeDd#tuP$>*Mu20LD@6z9Vct&#ymmgiT3He$>Sma?{8wqohwE3dg9 zgqoG)Z1@G7Gz^LmujD$z*v1RR=VBQE!x_?=A0ab z8~!kB#d%|bmGp)G2Kmwm|OnU;Zw5i4#W3dH6C%kw64J=T`mR``}EbsQMbZfr6X~Z{VjUZN zZ^{fg5;}8_-%ZXahpDtnAN4)3A8PNi3b#oMRv zKd>beIO%`4r`z|zoY?E^SO$24{o>=rY-_AB?dHJ4+1YDJq$I}af@5w)d&vRJdDWXv18 zA$?D`_>=U4KaY&EPxe6iQ*5q%dq{hG%U;S4q3_0f$lx5DQeAkE!gW++8l*viyV48c z^AqDw*9z|C<9oKqA>zjuyBnVpRjxM$NR##5a51Sl=x_9!K{WilYSi&agId(~r2U1) zv!;m^SDpzD-dx&v%HT6=_KKi+vGJc)!IwxLGQS#4=+B1L~|ayd95qyhQu=YOTJ{EEfI;CGe1RBuykJcpYah5o=cP zHDZX;g-EUJ?X0ucF^);h+G{b|hg}Q-)6y17V*7)AL(g{Swj{|bk$n~?{c<1PP+<11 znpWE{n(xrU_N|w%hndMF8q0}in!SU)ufA%PMLI=%n|2%OD%`j>P4DLBJMc!VWH?r8 zPALAbL=!k2q8pn+U)4~f3oQDadw#^43c)>A>^2&E!xnoq^f|RB?@|Kgm?#zkZ2C|w z$cn=qk4*uI-yg245(HP(&w+%4&CKS2e)8+2bTsxWy?imVwQOe#us-jn*3{QkRhTc2 z@qO-E<6_(2ahCWzB&QxYUyKd$J;3zQrC9fI!b%j*i8@^;Ot1ckRMp`Sq`NaiicvL! zEGVQ-@Bn&wV%hE)_}9>hBeMjF+wQh6PEOc174}TSSk*O&Ufw)<7D?rpUhx?U8w;FG z`FI5Wq&)wbhR@PBeNTYT-29o%$BHQ0v=Q3Lgc*i7HoJvt5*^oGTW{z3X1R1IL9d;o zu4j%D&uga&mx)q6t9;MDLY#kMo%ik^O+=0PnRG$Id$(+>v@u0NHE$HDQ`ZyX#QSOe zuOr7BnL}oZv(tTnc-K|iUN(;#qJo0t%L+^GcuSgKt@Y0vNbv+nO{3^sSdT_K3eEx@ zdue^C$QFby>$UM}%}oY9hFO`WD*h(ra6$hi>of;&X*ItJF`3jp_pw2)!t%T?l;fyh+f=# zK>vP-OXJrQR!3{ytXY0-CZjlmcBY6vT)DN`BYI7*4EB7)=|D7|+6UT61}$+_K{p@Ke~3&@B69svUt&)+*0mVP05vmHY!gwYjm#`PiHVmclj(&7 z+!L2#6_<))I)4meV`GT;-orI{Lota?(GEG?jg#VquzCvK8TgRXjGP| z(npH4Wa7E=ucD4P`#NU3-;DS2@08?UGMiJ5+z9^wXicAS%nPy}n&?jMg5wWBG88*q zaq#7_q=srDK(cnA6|PykZ%m6d9$edE}S^$^#xY9`4eUxJDCx9lkQBY$7~ zuEm4)uVCMRg1<>@;Ry*t#J801bpswQOwFjXjT;|kZqYp|*7wj%cVGowl3mosdTcl$ zTQwa}2uQ(xMzGRZZ|#rXv!o#Ssr^X*&dCkA0j2{BotN7^a(WiSYxoZloL+j7m;M5O z|8_ahuJo%i`NTuFeC`&8HzYlQf$S(d5rJQ7_l|xnAAb5aM-biMIVc5pJHy}537h$o z%}}I_*rHH&^m7Lt0#@{G#gS~56vT6KM4Kroz8B{Y?V)@oKS|x>n{^$u@ zY&d$jqd1nwCOwETKGDI2iv*qGz2~iKrX||WQ*CoQA!;-6JR20r5cdQ*|ZRvb>JXMu=4xVW*<;l4%WU+$_ zugVE7!;R0{p3T~y2H-LSXXQ>}m4Kw&IJnZ4)GO#1hNPIhlS+_Oz^l`!^Y9MdYw7_UF46}FCy{np8zD4B@1cJ4>I)0kbh#WyOzLpmw0keXR2X>$x2?LTjtXZ75 z-G?!On+Gi-c;L2<>(@AK0Ob$nH7aj0HkUta;jI+10>bh}N#= zaGvVm`T#R4q~oZD>(-H3eo^AVwxw4A%P6MVXu-gD9V$?_YuIO&^I?`jXnREYdKTW+ zbE?~p$Z%3AlcuSjK#&M;!YC_K;m`a#B$PErH*}suzUOMDw6(jLS}eg|;GkJf;~?mL zPPKM8fF_Z3)!Uxn$@d_uiZr`Pm7X$qAQE_u7CJ7d+CUy_l{>b|u{L0G!>W47i1AOAEN+u=&0e2^S986ak?E zu2!|tI=+Wz2OU-)60f#Vv7{50mfa$CFj zFB-O3`}3?;E@wvr$YLOS+YgpRCOga2gmLB7MMN)`7ajvWzFoJjMDn3EKV%x`Ep{@{ zBe5`qdGi{&r9%Z;Cv8g8v4W4-qI&)8Lsbs)#e%Bzp7w}N^D%G)XFT+xFXOEs9n(DQ za{N5{y&(#PcEVWCW~fTErQ8Z!r+pPO)WAN<-%EP6A2Vg@Wk{HL;oSo@<>rBp(91HM z;6>O^Khj!Rkq_c>CW&Zzv&M(wQH4z+=#izkY>Nm&Sd%kExX9*pwE@>~?Yw;Y08`vI zi3lErnHW4XP7lJpIe*^mm2mXam@3LlxIHR7TZYYg+p@jtW~omYClWES%KhL{O}i@S z6%|P2LFBDInb|UwarRwQ&-cefw`e4@-_B4V*r0Gw=Z|k5#NhE(I3HF+G?K($jQpw< zvK6mtX7tdkhmT&(7hbQ?af>b2)~f})YXYUjNUxJ=@5ImUg5-e1;nES5z8y6UlrM4~rIHj64jL!rQA2YG}T9TR`@*dJ? zdyTU_eN)!RhYUbk6o@HQ+g%9prkG+>UF-QW9>_VGwv-oawVlvX zhHK2lDnk@(MqaGJb$9-yF_q^sH>UJk`RgGMD)3PQ$ek_NCNF^m4f-jxSJS2%yY}g7 zgVNRG9VrMHG|IVTQI$1o(wQQ`851JP*}fMhf+H8H6;Y{Xys0i*_T7({jjEm(_IwlE zH`Q5hjNjYG=VVMYJJfsRN885AmigU=$wI4hLy0d#(q?d%0ipteeTKcKhBV~9AJ9wf9b^?MrTG)DxFjG>JJ$zLuSZ3W6~Bi;`(S`u%*aR z6xn3y*ejm~?15;H|s`Newce&D%lQorH+pj7yj99BgEu9hwd z8n>{i4CpzJn#lTLco@HzUKy0)`~G2XH+O7g2+HgVh`RS?UERQZsDXaet*m(xJ3+aLGvaIrQ+f#_RK%<2^SEx4I5T596!kOCB^z^T zl<>)~*2(C(^QS?rDL*SEE=;*5kF~iYEZ*p4&anErV=`UAOA5B4a~YD15?GYXnTIxy z3D_d+C*7P!D1Gi{wP(93uM&f2YX=(1P1?2WH2FQLa&xqug@xD`YeJ9FPu%J!f8kNj zm(9zY$md1!zvmH7%DDnM0JCI3Z(%hX=?-mD<7~7^`NEER0&SN0SlJ3iIN_>FVY2|ZL50#Vd2W{iMg4WtdTp%kX=qL%-MXd5qn;EtfCty*3rX3 z?3sbehc>*c>u9m7Td5ZYb5F>#SoBi>SD%z$ed{F#?a6MdQXbae5oSmB9q(wT%uLcL z@yUBD3Y?h~i#Y8KL7U*SmwAx}PY6-haT?Vs=IhxyPxIy6Nia$DsbRZ`mbK3^)h*+& z1JYrsonE&$vDZ|>10KKE4JXcs`(T6>tL4+-hcLkeYTT8o1sUL?|Fh)pxlebiS!#GW z6P3*Q3tTS?kAxRwsh#pm*o2T{dpp0grm<5d#{}P%gA9N>XY3$SU_e!&$H>H}mihjr$wEWfy72RU+$tcZZ>Bi`HO5rydh!s-qC{OK@UY5(yWC zkHf^FCaE-URwqR^UF{<4WAt*^!)#6fQLOES(9dxo(Mhu`@6F3j#Q<&t^VngruWlom zr`~R`?W$@c!r^#UL$$37mW5pR&3%<-+$bYql`bCwSE@3l!d5!X@xp?gX!D&og3%d> z`aElK^F4$6_c1n(;m!1{y(w-nZH99QCeyJkL5phxCnUod-#XoGr=iR3yC8QMxz>yQ z5D~H{RxhF_VFW_uPL7S?LV%NMCzOG+b4LWsj zS{8(%3afn@Wo)&$l1NKW>lTV@W3Dku5uw-M5=zwi+fve#=RwxY#VuER2qX*AY0O&$ z<^C=Ofn)oU4#IZK39&Ol$YAn^J3HRYchbBCB`L3M8{TyNLuL9R(#T@X1OkDQA z+i42%ou4G3MA;iR>pytg2)0iXz9oDjY@2S9+1o=@I>$4QsAtwN;(Au8#Fs?{cs+-`dwf=71YH(b+Yt~$qNy{@6icWBrLuCEK^PxZ=i6X`%|8mvL8*kMt?alrH#_ zqay)VT}$oj84?EinQ0oGm4)h*Q2`Di8I~eD=1JC1JGqez-SdT>@XAL*0FqDzlOG8 zz%|Jkl!>kOuMD7HK7<8{pIyZPaHjYD##5Wfom!$bDP;ZwYKW+7r|T=;8^^?x6m?3i z7*oMVWFv9C4st;I09*eWh8twX4BlBo!`zOjpOVRoi3i&l8p0JfLTnYHcX$^rPf%SA zidK@z7lW%PwPNocY)Aa~28U5_Ld^7hWq7Tq(PkB2vL8DOhn&Vj{J`0?%@V(Mi$^Y} z@d0)44Q`ZHe4?=OfD-nKH{PsAIR$T94%9No zJ`g)B^MQ5wScrAgc$dZV2YdiNY#25~7eLtiV(Hp;@AG#K+EG`(5N!WL?U+{K(^C!5 zdW=C`-X0lXTn&`nDT=i+usjTk?PQuvdD-rZ;U(nup%rDNxOz>NXu^nXBg0JCb*;EL z9-gUbvV6?XzW1~`Gw3MHm`H#cOSBDK3$fRibP3~wGaR8Gj;!Qv>(;sOb)Hpn=M+LA zcuJ&gXKCQv5%e%B+?-X|>bVGf3(Q#Kr^5MWlHn?c{FvANvxs^2PkrCt;Ma1Nu@~cv zL~%E_%=2#mD=%4lm6;aqLF^977UjjIf->msMQ65S8{SlQ-%MPbZG<7SIJ zzCbH%^4bd`Ht-`Yz-QvCYSmrS3{^d;~=o#%{}2$*OchcNXCRb)(RlQ;$M{$VmQJdLtGWaK`MFx08PP zW?P=gvynEDN{re}l*r2LPFt_+%AO}CUvN3bM9`YBVDx|*1?l|y1bvnG$v1zTH20o{ z=ys|NOT?(<1lH0)AVq#lw zTwIiHj1Xhv>Cde5G7I^?kp14z4KJ8&zu|(2Lz;}kI^BV+w@FV8naOni1RtoVHm}-4o^c@|K#GYF`Ugl+rL;+>6(c zJ!CIHEhd=g=DcaVTvPQLC=TD?*w(-?`D6a73xbUp!~`-9QoZ@4`#*RwEAi`533Cx{ z=xzKUSio#ExTCNuMb|6fac!PYShQ+!K1_1y%8v_k91x@zv7iU^YESrQUj*S>*FDEh zVJ-CY-%U;9*Lr=IKC@r4bBKOTv@;LVg~&pDKxKGDt&?t@-eplpB_U=Gp5(g z(%ZjSr26&ftaLt+LrO-tC%N!C>p>HFlH!?ZSm3D+gbTuNoE`KxsQ5jb&i=o;q1H?> znJJN2c%Ic86K5AdjHyxDj^rmZsG(61+vPb(ioj1=GD~lGuKFO>WhOC4V&J&xr+bz`_?yi2pR3#YB|gsO6%1(PrN<(c(IR7SMEg zA0EG>wEa6w{A<)_6m#m{Pq?Ja)Yd9hO0t6sbe(#>)-cak)Y#rvJp3Ndf1iAygO7)m zRo|29RDzltxgW%(x>+pOG)KH@SNUI>Yoh-a=lI`(wSCs8V;-F7nE#s|ASuX(v4}MOuEZ^8v#OABiUbANQ!twhfpHqIjl*ixNmVu zm;Q4Ge+%<}OX&aW@6N&P;n^)to&DJ@XU{0ZKh^#vNy;3EK5L?-Ys-bc_u`n-OVFo& z=4DzT=w%m6nS=T_@Opy&sw~MrkL91Bb%ggpn;qBf6u#T~e~vMoav;0}7ek=rpJ2~~ z3*U?6$U&>}zi**I{NESdkIDZI-7i`CUt##aLKp(>%=d*6*qcg@+7{Cn#=Vrp(I3-3 z-Ta7*E?ulD^#CP~uISS=Q*Ms7c#oK|azqG24!Y`-_ZaB3cqQTl->>9(`cID5Tds^2 z>n3O56}o3F{lR^Y85gsjLw8zWEM9kea&KFoLl^$G2=WT`@7~Hlyyee-TFCzP zxc%;ul<_FMyYswr8XEun3kTkEr}l3N{~*TgS+q1&jpp^GCI9@30^V|{e;)IHw;L}& zL=xWJQ3oxJfBy9x-tv(LGHU;vjsKG;s=b@S>EDxEkAx?%m!GZc{NtK9n^IoBj6!$M z8044A6e*Wl|E^m!Wua;X!{|C)t>V!a>%W=NI(qmV)a8*%%HhK$AwW;==MUbIi@g5u zaIM9U=N-le_U1QhlG)yJ-fQsMlixRbMfg)8EvT(({K~1ZlSG`YOR7tFq0?5H5cGm< zNIqd|e?aN@&*gNWLilQBZ&OSB^j@-m^3fdRy9u8Zr1H3LSEyl&!i?-aHtO7V8FBxb zIY{-rgtGZ*{Q45pY*c&2Ud2|$mTjnR_#egbMTB4DhxkE5;$F6V36NcV+%()Y{0J(8 zto|^VIX?JB_T(`Gzk(BWScuj01``Govps`((tj@6L50ijy#Eue`v%mv5T@MhmF$%) zEEoxuEB%&R;}p1JaN$PZzy2qVlJUu}OO}jOZGQ{^$r#rrpj zwk^!0|67hv65))+$Vg{b4*m>91)BZ)K4o6^il_g{i}<%X2Fy|isErChV-)AQsV#v!jv{^QjdSNe-0nx=iXLe4`J9G{#3=Y`^u>QJ^sHQf%eSI_U=qde;B-u z(g8~2&=KBt7{!y~;Zipn&ZBaND`2@!My{09(O$`?hYG{ex~8(TD|+jU>KgoDI--z` zFdq07Ykp{Sdr!X&Ln3shK2$JJ>oHb3JNe+5Lz9%W(MRGH_rCY-9KBK;wGFk*jDpHl z!+rhHm%gYCm7z!_#??BHBo)WG`mI105t%MLGW}`e06OFa^d2 zAby8Fw7;12mHuA50g}d^Z>h(hpE=C09a$ngd&6U4()qSb2*esZZH)S{znc)5(05v& z6(TJ2o-sW*!KC0oGJ82LFfxya+kslkKxpdhk%7JO>zSL~3 z?vbG>f-bS`uB0P;Cw;G-w(i?1pj^*oUt&g?p6x5UMyhHT+g3FBb}H)t zFnR~R-g*r+V7G+n-mw~kCuX(&xajj$A;_x>yz|Jjrw zBF&+frSTMZD)#kg`tvQ~+^><@+z-MvrQe)uaHLfGmxa5S`PH}T911aX^fzUReNUSJ zZ;l@+p&9(K%AWVTO1W_s^TEs493s)h&+7%T;vmv5G#>GF z=Sr9`N~#fi`eHCbJFRHbgy2DHuSVJxmkJx|@n(>{dJbv;1 z)>1f;`Snq!T~DUI!ESI|w8nKh>1|&1iF7ZRAhnVyD{XYBh$XfaGegg*kEL5HXp@Q^ zfi~STRLMZlxAE^^4^`SGpJwmKJi6>6IV}Z6K5@9Id9(L;Dv+y8$d@~MqEZe}p;TFv zJ?LLuS&Ztj_9-o(J@5dFy;9q4=rVs=?4m2)kuMs)J>Io^LHr-#XG>3muZ$Z;uyosMoIRoQ8{u=p;P~xz zo@*rs7rqsYdUqZVsTycWF{T*PS3E7+*c9Yr4gVL-)0IT34&p>E^n}sqEF(=Dft62k zd5@N~o~fJzGo}SJpiBFL>`;31YUPT!@ys}pA5z}zp*#jRY@1#uHyy-RZzM+1?XxgI;ZNz-taHVo#sOy!hd z8wdfcEG-JW^^uC5S;}VL9;KlI*OT|VN2Oolg`Y$S5=F#ixM?pLkt3=!6)VM~$pD}- zJ*=v$jk)T>0~slWJr{YALfy7qf)`jJJ8m1RlhS`}?3i`mu4$~2``q=NK>uP2QZ?or zRhT%(k^@GVHDlvkq0hq_%)$hPF=af})VYeGgxa?{4lkI`wZ367YxcIre7kJr;|y@- zK=830GG|&aGIme^AB{~z*+N5?37B=y;Vm+R{hYB63DT&w`x`&$xEG2 zov&iS4EfF&LYYX2LUlq?;(WNH;-JH6LZYQo-B@XRVdrb}?p&*!N;s%VV(rX;1_BmR#hN#RXsiu5 z(wFStL{RWminYIg&G>JMv%&u0MrUz0S(6-|+zfc4yg!tx{%$x#=)*yv{JZqtu?>Tv z&<)w3Nc}o`Mr0xaTCB9f6Z}gJe-Ca?t|yA?{ug$(uhvdeY1iZUd5*W_0?}9>_H!NUX!+07=N_B$bWTR`0)T9F7+!u+)vfe*ybfuX`7Md^h@*s$LJTLmpqAK zpRmX?rcZq@m(pnUUsAT?7^%N|yW_MFLPu!AXZm5$_tR3w+o8hqA>Zh#=ds4x!yz7* zPrbl6!Xw*gD!4!XQq|;pRlWFPb#D}^@n7Mu9y0_S2uZ21X1rXXZ58Yw`bLY${V2)c zt$%gRkWS?*_1&dlN67Q(j79^_aoL`fdM(o zu4R|T4JHo33(=!YC3ZX5kzYeeG%M(LTpqdJA>E@|F7o(ho}`$g5IQ`>b@?QLlGkZr z7eG(yL>GTEB9uUdgLHJB<})&->Jv9mF#D{EsWaC%hHSBHF=Dq-blhvWEH=c`s#~b7 ztY-YOTHr`qWCYq6C_Ph8d@l8Hy1nR+J8eu4v9WsQw{Xe$cQQ-kB;qP6I}2gee{!!6 zR@y4gP{M!tAQ4>umiggy-OtI;8I3@*<*lEAvBmY8qZ0h&g&3zmIh9v;(~1f{U_G{w z;)}Y&tl1H5IRo5ACZhg%W~qWy)0>Q#hHqx=jB{2Wlc-I2MO;%_Q)3SEUO)TGC_RB2 zCBxRT8TcZvT>ueJkYh&zXS%0{@Nvp4P0V#4cX#5M!OItXYZRWM*`k|iD-Ne?#ste; z87V!N4XA?B#ZfZ8MtE>^t|jKOCE4bvkTSvaK

pmm@)hS2LpaGg0?^JjZF)+N-?v58pS;Du$pHDK{&DuwoPE3`t)D_9DE2FisfW9c}-)8e9-hZKbW^_uU2~PX} zAFkdqEXua~9yX9p=^O#28|jh~6{J&Q21Jr8V`vy+VBj6^ z-=p{ae~)86%?FOTF3!Erz1Ci9ZwmU4^`)Uot4gHtn(_y@^zthfv9*9=n;#dU1xGFk zi5-rnf%grSVlKp$##j}(EzIIPSbJ(y9w@GC^Ab4{5o-e^xx~X@IstaD7rwiF91)@d zj`2b6)co9gD@kVBylD{5Y1`HG)ycJH&;XV{>?q(F}a z1CDlySjo5SKUe!%(#M0+^NZT4VN^W4#QdflcrO?8jQ|EDS!qQFPjD)d?TiT4GdR;_ zHatkEFFYQ^EBJB%q3P-{*GEt(Fo*7DxUG*jvNcu8?67>NqY!8cFLDbNV1LewZ`3CzC+Vm09@ud|gcQ z2nRX0pMv1HQ>O;0T$r=dX{+vt?4ArgzEAQeFlpd0?tPOCsl)uFP@ zwN;jHX^p%~wU7B5e#wYq%Orc@f`6JrSkQUxIbqF^dBQGH)s*e|G}~6RcxS^#ZN(o{ zbXfjpgmEQthBQsIu0@pJEAJ^a_&?2u%r+>OKijQ`$tb}iB+nI_GxqCGWgP%!hud4X zCY!gsLv28!g(0u#{E!3)A6;Fv0)TYtZ^A{qMHSx5V6Z!?TQy4f>R2uSonC-%>kB(t z#_MM_ByaZ^dkR>8y3jeq;04+UJ0h-YiMvyl%~AQ)3-isStQpdPT`OZNAmh}X(nv#} zwoNU`qAS~M>~qv><8}(yw;%$O2XXxr%@!c8PMY|>qXRuQm1yU75d(-mM1KCGVkOJ- zb@;;4*6zxvXk*FmJ#hc`kzmYLd3f-@3TRNq*gKW@)BD*fmHY2|*n+b&_mE$>#GNt5 z9!(T|IuTEneILz|Yexf}^kZ$%eVXAc0Ff5$rU?9ecwGbQ$Rpsui4$w!5cY-eH9}AL z8m;ahwhi0O7!-hkIt5&qPpAxm1s`DDF{BP3?>zh>G0t%HK!Doj@rnomsTC2+K(s|3~#nG zUMtBy|H3_JN?p|@F?uSmae|#?O>J;S(YzuT&w?@vw0rY zH2hGqWUpZtU2zo-@~L~cjD0aIZ>pmPFy)gsX}(>M3Z!&kU7=1p6YEUo(SXNDx=!bk zJ}_hVN8@un0)H3F!J$f_UMiB{&GD$Z7xD*;|Ca(X=D3edORP|yWq|8!yvlr*7{!%j zK6pO@eN293%1cdLEh@CkhL4IBCceZ(m^LI&_0j?8dC}Q_AME!|&v%4zJl}|Zw4hv9 zqks#jVKME<8nmx5>2^w>tF;Me@z8Txzcn|NzJR!#>4@LnnqQe6YD`1!_NoptvN(gv z$T|_aL5GiRGd_A3f;>SinH_}4_4rj1-UZt8?yn7@LkqT21tL-<+PS0oVrjlNwZIh8Bz_S(mMZL3YM&UYh_uiDx6 z)tl|-OfD-HaAq=YHpK%UJ3Ykc8mlUE66dCoQ@z_%wF;{uz};8xRP0XE-N2U?+Tq!y zmzm?nP<#+&2o1JaTNPRC-VhyBhlYBh&kE#?=Jh5c#iR&Ku8!n69^)QU>nux(=)L_5 z{~*7U^!1#LCF&{i&}4Yj4E2`3TEmMv%HHM1=k|r-<~&hkt{%m7#!vuka+JsTl^heA zn7icCTq1xRC3{>}^MZZ{`WvATgy*dD(45kK2Vc4>^PALe@ALLI`fX1^*da-3Mv3YxRbq(o}JLsSflEN_X*9`a?{nmD|Xi(iAYZLo6xaxu z#d?9I7i6`HlroUTnraT=Ro3ges_^>I)%mj7$9;ULH#v7yQL1E!H+YviOW>)dN2#7p zx!>&_JGAXY!hCm^8WiJR;8cBSU^Z=OII~8+7xZ?%zsVl~cA3&x>bfW^I9&fCZ+<7` zudIg%k00;yjou*hZ7tfYqu4T60-Y!}8_wFfTK6W~7L3-E?yO~IWQ7y}PZXDNk9iMv zUplR4j%7^YhyTHBqq3;Dmhe@p{XH;A5Az$3O<~SQnMe7rYzh^w&#>%YKI@AwDbib` zpWz_tBKO6=39?K~IL{on({H=YbLpPAZLzzZlh5R1CY(5%t-Um%e!Ap@Oi2?=pf*Cb zRc*QfSUkT`;Ke;Uya=^S>?UZyfd&x9t=bt+?BU}_HEa6Kez#4 zrQ0d zV2R^6r(a#k?Ml2Jwj!+upTtPgQn|67?y#QF;bzfkJ=!<#je%KHmq|T6=ZTPO)Rxng zy0`~|PA{GTfZR~z=5j*H(0np=pxd3)^#$K?&vk{`LNzx*u08*|vQ&A} zO#`RY{9Nnp_=W1{hCaKE*Vc%kA&+zgUjX&I7rBOe-GUL0e9LHVGu=D<%l$&R0`)rI z(=RSVip$nnM=%em&wQtKa3Vs*{oOE%_k1MPn~%A~_X830!>vW7e%(GeZBeB5F5e{{ z-xQ9pnvVsxu7)*W1ZK+QdN zOur`Pw>hn}RiXjx4Ih*L8>#-kBlRNHMgjHq*Dz-=dPCj$V%B%V0(<3%(LUL7Q){kl zd7$&&m$G7)@;M=iub2|g492hUDh{t1xDUCTG6uC zrC;{Tt@uSp6GzH{gk5PZOo!gvHa3tAu93r@h^1yNF0Tm#;Lh#Bs-CwsymYRajG=!Z zHfj{|r;n0&4Po5Hb*R+I6Z<3j^;2SDLJ1Oi#=zdf%UXd+^-c2B$R*-}%qO3W3%+zb z@2zQYTzBVPh+xG8C&Y<<=V-mC47X@LpSm+BfZF{u(P4+-7Umn5$kZ81ebFDVA^ga- zK5iPnS+%mZHFvFb_zrbZ^C5L4Pfg4xm?3Hfd+he=7ekN%dh_E5g+#CGvKp~UescsO zt-tQnPqRP=4BKU-J!p964OG#|k|+(s3WJ#u4G$CyVE{$fU~*xKtD?>1KkSoFz8Z?- zR=l0V(E9$jMTvq3T{iwe{e6^&!kFfMF(9qNmb6TA+iq0dNeLq&`5eNg{+@@Eb(#Lo zdj6YR{dJB;{cwx61*vgY&bkzL&Pvm`%KJTyzI*e59c0?g#zRLR`~-OvTmh*-mmZIr{b&VQ*Ycv0liCA9O|H-9r#5yq2uX3w)h3It9orS6wrLeOF>m9HA zY8ew<4U)mA(tpHDpBox(rNYz2o#Ml`*B$>1eRK4$a4-A6q)8sy(F|+I(|n>P!tbLB z^j~-gXxCpFu|~<<!f_>ax)dLIHWTd1)@P89dAEl>PPsC)hii#1u@s0=K9d7 zU0)IwaBdmt3i_V0o3HgkwCm;>JM8tZ6s0uIz4j;c>K3J3Tr(D2Cr6NRM{8BT;*k7} zhf=-66Blv&`DfGG79%37OOrY=M>K`H z9&d;eH%eIQ(0CC|PrX&c%^E|HL>m=kR-T`k^Z7t_5dK%{vlkmGd%mSVLkaFw^e%=+ z5>wKICJtci<|`2jNg%(AKJyi4jES~A-U}`$wH~dWSmu(2a z=KijVBqQPVk>~nr=YCUX2LCi`AcpdeLJ|%r_aC>d(WXmS|>&M4RW0! zN;~aic-70X9oksuj<3td{EfkAV^zH^)-KH?wRUxx?}4@XEzz}e@~udc?-9YnL6TyjeT!J^c2Y)B@6cf||8P%M z*7(;Dx4*K2ZCVunv()-+?h}gtF+j`CkdX?>-s?*5U2$LzR<|9w205Ho<0y7HDUn(@ zD`o10PaW{imq|4gRI7a&IksN9E;kA)C1x;XZ={ZUb!4vus zN0j`9AZAzGRITE6 zzU^3-f4-tb%$_t?aoTHO{>b4o3)mjCiDj~Hn-^kx)*agwM(n!rAnFzq$L7y<@`4*{ zTLtllY+FFPY0cG=8=JK!dBc?Zq{*gamE{0Cl_Uh~*f)y>Jvt(suz`Y8*y=FV!UjH+ z5d;^fW15UaY^&wC!^5ChcPN|~ZW;YH2Pes5NFb9qSx)IPxM)x;Cp`u^!Mtg*d=_!q zKbUC0ofsjH)Bh!G0Z&3~PV9TKyw}UEA7J<%e5ccCD;yI{=@Ut^X#2UO#%p+I*haoM z_7A`H33WLT05$DV*~wTuU{WRH6CWi>U^tQBFT$RRW^NKK>`bY`w3?ZOl-m4`+b2 zkLmLe!h_p<;ay*zsLuBQ-=T=6qGNcks|3Ruvyz;eg0!dCDj$zp)9Pe=|B{NAv7XDS z8lCZ0-=(JU*YC$}WF4Hl`W5X*k!H?X} zx8tT&{NoPKoomweyzB@{e;e>LLR{vmH)VoIQJo!yIcs;8{sZ>ym zu@f(u$DzzqHF5g!V3norQ?XCw<1U%5ObJhZup|?jacx_QZAUYIeQ0! zwKwQuoX(ae9o4=yY-%#NXo0W}C`(jwi0^DyZ3i>I(U}b{o2RZS`nN>;jEX`4P;38p6rzUhu;jDg ze#X52N+bNmgdF{@0h)zOmxi9&P9v%5{*jPW{T_Vgx&>1|Ri0GZ{o<|73}Q4bTd zV~XwFaum=V(H9^f`=)4EjWgv`bS!5$#89=cp)=tDsdeeb?dcmca)-`WHsqI=gd#0C z^XG?#CcOztVwKRUsvF)5-p`0%e0QUu6o>J)aSMo)pSIq`L3KX@f{ebBA1;l1KZNc= z_DWwDN0U4zXn#+&e&i#YjN<9k1+YP9wMK`x8)Thth7oJv}*l zGVoWB@wb323ZJZ~Y6ntlx7y4f-OOl47H0^G%5E2p_f1)fEm+rxbkewHN4?wd`2H_G zw1!6w2X^+?-O$7#OMi%6#i8b+vlnw6zaukU8bX^oj@a>XTc5FB* z;i}B4Ayt*0g)BHEhxM)!#*Yq7?r05~@K@5qD)p33WI261cU!O)-{Fb==OP=t6-qR6 z&`JBPc?yWVC(kYypQ0Wutsu>(PEkp)FGqkqw9RMbGWEsfs3g;X*g^c_!IB!SQi^IV zFcTo*_-ZS~bg$cL5W$MxI~Di5MRh^#hsl2C6m-W&dOU(OYM^aIsXD4&|s|pHE*A_%x+$E+h-|kc)6`-S1&_kSXQEe)ZPz|NS25);kWMF;L!@C3M zKwuuy*8bdYD!zXjY^ZDDP(@&9bMI5!v%CnOoi6bUK*?Zoous|5e8+3@e<1|XsFdQx z!LzAQRIzHoc+WW)XqBNFsCk0EpB%_-zo`c*)3^?o`XwL7d3s@_4Bf%g?LRFI@!rnW*CHc;b*p5^!PB1Y?KdfX0{2O>&zHV=1Co=R$aI%FTO6q zcUMZYgUDDX%^4odXXANuRfUJLmTJD3g2s*N7KhM6&P zbuYe*pS9RiTNMD`gz;6oiGi5+hY$LJUctQz1{-m2dBBE)&GF|(Jt>y2Ed$hg!jicD z4Z)|`Vt?tI46I+3YClC;7#6}meBNfj5hVZ48u}}u4K-NBO;S*((MfuCtB*kDWgTo8 z5mB{ttmp3X1J7tkY z0R?BQ?>MwV%J@5Mi~sGi0)KZ|m!aBXsMxf^#6^za=VHomK1#YW!IgghCSvP=q)jvR zAfS=7FkN+Xo_Qtrt>eNwhnu4$mKt>fZ57xzmY|`D_rsohQ+o$Nr1HoHW39`O9)YyQ z#7H9Lz2?yB${||2td4o?sg!-O{uoQY0)z8yDHhw?bw=|6FR9f<;B*j64Gl}k@#HgT zQrcT%qpNkH1Q_sYiO+55-B)9M-&M7ZUl1SX6ptTliRbXxN${+=MQ2!>rbS)XqLvS? zS6}CW192&tPrJne^mLXY4U5~oLQjk$L{f}L{z=A%zluW?#hU|xz@3BU{^|XF{DJH6 z6g`r}pP~Q{c^+%Q9BLqLf(x{IN;7caN+UBfodqvLP;jnse2XFgkp`58wFyXF*Ph$s zBiohUH6fy3^P}5s0_u>!t8Y~&mWN5iBxxv0Vrb*#^>ZW4UDTx|(UNbH_^OTwKmwnX zVoz(kY)y4+|BUf9XbEwnesrq?Of0J{A?O_xH1o0$wByu=+yqfUw*5Su<17evvXmPI zyL{%o?$RR=l!gD6PIo8~@cE&@R4gj=+@+r!8p%-PZ%SvGE!clkr6PNs@>uBBiKA95 zW>zr6|21i#s&i(2%t)lefP!PI^0wq;SHQZL{QdH0?6XrEqYLG(-8&a-*W$3RyA2!j zNr~)!E8P9sK2kR5Q(D=fwSsh5A`YGY;I99=#=OQtM!|FPuJi&x1Ro8@^Y}c_cMVne8M)?zAm%G=&at|l6W}<+Yv~- zR3he>wPAkMd8kL0<%GhMF06ow?u&K@#Hz=DyYGmN-6yqe{g_&RB*5IXucK;rhrm2$ zBd%3t_PvMEF{#kKz(WuD65eeyW4B`~=enz~L+D1B?B!)4WwVaCxoe~UNbCpR0adY` zEPdj#^8e+KQN`bVd0)L+l)xL+*m)A0?fc}!={4zl29|EdRduVJxXlOn?Xyh%>$wO1 zS^WA(ubT*Ch4bdIG%3_SIAa$cb<-dzw{0V>o7Y!IVF%A$mhu=JS+9|EdAo&=N9@wr zpy|g4gvAEfNTwt%u4~917K)GeO9kL>cr~52af1wIZ)&6ZccK0jNk=*!UuV|LNTna!=-_Yd zu_-e2K!D(C)(Vvq@rd32Cm+oAM>{(lNBX?2MX51S6!+!zi|=$hW7rI@s%d0-Rz5O) zpnjG=-{Hu!$eKS_|0H50SJ4j(D(0TVO5Wy*qRXr-beF59QSD~-T{L6I)X9guSV)$d+JbjM3rx1U*IUv(9e z-F~aY|E~(63^D+5$9KuX17NkY(4h-qxD{GcsR}u~{ac z7vzwFIYN$7t9D8cziz~}kQ-`CZb+M-`un9J*`A~sAC$`+M^Ik()69u<3%x4*781$e@eSn55(2}MLk}xjHdZx=UTVh z=TeN`nljI>*ACUEO?Sd0k_in+x(RjsnqD`JBIV_pN&|a9at-_@$`)5t$p6<~)vr;| zKiQc~|#zLc2ee0l+lP^}99#VDtAzzAWz zp|I(O>x@-?g2&k-B^f_oj@*wAQ~ncYIu8(*J8f-Mb4e&>#`6JTkbA=(FF9s5p>*DT z5@Y%T7yQc6SbgrU!aZ+&mm8wJZy&!=4D_AbICE{LetV1T*;QkNb%@&ruXS}?neX)V zPGeK=$;OTH*)93853n^~M}+ozfW7;~1`-`-E=o-wj#Vh!*o4>&)2nUmOqXpLY8E&y zb+vc3^i^FN_43e#3W9x9zCQ9eR=L;XxqC8ov8{kT-8~61>0DLffunFil(f+NhZ3i#1dR?YO#x zS5kiIuaz;N=}s4|mcT1t4|?z#E=OykR!|kp(O z>Q*$CS@a>U-{HP@6=NgC$K>eGd-73#{@Tv|yluM>KPJ_ya1P`Wqe$oCp<=aVzYG~4 z7K7F&BumUTiLLd`T|8i)?z1fqW6VIEqyPK-KcrIPi^!r;i38$#2G$TN`mb$Qm#pa` z_6#m{zf^z;?0d^V0R%`+>0;4fHo_t&#gR-TVW=;s}t(jE|IdH)CgCeVF&sKZi z)sq?{m!=!bJgYuU`H{KSg3y4q4m!*qd1ru85rsVX6{E0+yvTT}eoiJ)9$z}8`EF_ML_!K#O7%Kql z-vk>F=C`(Cc>AfubSyHqvldi47#X%G&j|gxWF~f{|8uLN8Zfb_V{~YwZjYMRh3ZlC zQ(UbpA$KmJ@z!Bv%W;)I$4lp0V zICCR38HrpwOD^KeB~4r5&dlp~sOI)SWL7hvU}hRQL5}Q9Aarch4O1hFSa#12wlw)U z8sRk*0jn1)zs#5V5lU$N2luuzW=p|*F22~vqGG%H@kffl;>gZt1f-9a;o|dtAM9s6V{xYYLU8(t zEZZ6e=lw~)%&*u=XW2&aUW`2Z(R`?fImae*|{_52J5~X7KciL*8Up zV4JC!zBs!$UfAA!FrPiI1TWyc@5edI5h>UUNLq?RFxR}W@es@r)_P89etVytGq@O6 zes|nPIXPEyT7hUg)l%@07uV$+)PIzYd`TwjJIs-5 z!d9q@&zDp8@mU3Qd)HEZ&=NF*((EXGKlIPYtUR=DP0s&Sz1ZbkN9hCEgLC_M%${{0 z^3lO8OdPTYB5ou@@bSIJ6Pec?o{J$Hn2=bB;eS;VMvs%12MqtuWx^ABPhKT5hvY3Q zp)7MFPY1?Tay!Z6?mReb=0sidW#(5MA>Ut+fx#Zmjh|B22`HIX0O)A+y~r<-!USZN z+nZTa4?y7_{@#x+bNk8nZQ*P@u=UH~X@HTQHe_Fe9&uB-0nfU&(7TrAF5-sS7?MtEoKS6U#C#ei$z4^bn3)pJi zjE}qJobG&{l{YWZioU!*t!cTsvwo_x-@*0Fj-9?huS?$f^)Dfr#DRDcC0^B7T@QihU3?=;jH1hJ#TLt! z8Sfk}n@6 zcX+W5ccI|#n~D1V&*$Nc;oLM7Z?p)$sW$t<5d z)5fM~La5C;0BsHPTV8f^lDgYH1Q~`7m%nDsS8}fB45vGBBv~l{fzRhYji-6+RtPAp z(n6OEK#k(Ap?9eszD;|`E{qEFgEqQz9mqQ_pK1O6!2&^v z24P%#N2$W1xQ8sZPph}l6FJL(7?t1F)+gO_g5^}$KaY&Za!FV5%ee6!%Sabk#^P^}CKxY@CA-AYRKn2Jw^Tmr3P2C{0 zIck+KOB(<`y8lLJ9z&e=s*b#)m7`K2mcaiy3dM4YR1f~@OlWzLSOPncGcISp2;-Oq zd<6lIwyQ?K>FA8}n<$DBg=wEk!IJ{vhkmUFW`S-cAaDtIWZ|*?(b|cLY-lKOFV>4) z#kdbG{GzDIVK~L!`0ghAzvR#2sR42^&;6gT^4s*|Wmp1|KjeA``cb9wU}RC$Hf7td z=A|8f9xlskx1i;U@6Pi>Kb;%ep!)nA=6V03-3w&^;j%q4nz9-=^oP0(h8by*(Yf90 zgsI!~x+gPCnz;lt`*FXY6+rfN39- z*OZiMK$mtkhrSu8t}#dRp)*C|YvS(%fDac{7a9O{rVAtO0;qkR3T*L*NP0uuPl3B% ztpmkbt^OjHZYtc@sXw|4aV+Og76sKL9ATTMTx{&hQYoesz}e~> zwF|a1h`U;fky#ez0+*zXT0mOs0R>3Rz`=t)&p>t3exN_x!&_%~ZfJ}&YH zZm(_D{Np2f^#CZjKFeLledVhk%lb)4SbO&Uose9X>1Z%v>WJH_)lY4Z`fXFP3+td0 zxd}-jYmL*!km@zv^tLYP=EfsteU0@VY;iH~>8(SH7<=B+_XacC=`J!NXYdgY9Q+xa zov^vVO?&fs^OAuYtdRgv#!w5X;w4tc>4mTVEJF1Q4$bxzB9%TB)s7CQ-#3*rONl=~D zF}B@qCds`q{4c~ni8}TkO}V4KvY;m%`5f&2fhZxD2h5n``;@fqrPWlgT24)Fe#19b zoEaJr(fs<>^A9@v4Ne0F4bvoJCM~uCBd?joLdyDI0Pk50^xPpF8jpCv+$H<*frUbI zVH^VXE<3qZU6MJTjS{=ftCk0PiA&crd~BK+=QC#ouH2>Y%|l2r&2i>32hN^Eg_Z6* z2V0m2SD|pm(oAa@e3XCx^DiG72gMrs0>)K}m`jozi=V9~T(pDQdi-G++WLq_JvZ22I`1jp^hl1Qw7`Q7&GK~u4hrn-te=beOl#p9Qj9CBr5bw)l?ExCS*kE zFW-I(;e4l!*o`vAaCrJRz%BSd1ed1vo1X(vrcUTv@ND=l=< zpw1V`;DdYlUebFn4lQf2H~6@r0z6A*J#4@j7zo&WC)fyXA`hMg+gaC>c;gD^Z2!se z5>H2wJIkFz*1v6&44=@BC@Lbd5fnUoT;8H;nEPmW8>`=|3%}$_X=S=QKG zOqIMYW5T<@-Z9F@rWIbss1404Np8F13u%e4_n_)RDe~uzRUD!xAFGazko~9(cex0@ z^iHEar<*7vHyR>PaWR?M_n(RfXySV#xpf=J=bYEj{hwZbf$0grjDN+UL1yT%j0Qy( z>{}5bj@8Sqt z*;D!ms37q``C|~nZmQv(Hg5N|9%+O#MG7};5}6y&xUa_M;_w#uX@pl=M1sQyqOQMb zy4u>pTxPI2IQxYZ&G96)=hs!*Bkm#id!WG=#ma_lO?J&Fb_yP*ldzvu&ZPleeqXQ9 ztOAvP3+WV5t4kS)wS~qmm`F6*iEmw>2Pj@vFlipOu5#^?`S^NX?OdFTm0o^iZk}(t zvL){c<1(RV#Dn2$=O}WT&wQfZah{1t!p%w+J_MiLSA6~NL($%E{Z~jtecvCFH$abB z$;LJtRYdsmz&sGIUf)?)cG=bG1@#8;7E(={g0)Ee+o|O%ZMeZ%2ut*hWRD0nA0AX` zxg}Q;!N|u&?6j|oO)wepPQdS*ANlq;p`WnB?TpSqE!Zy$R2GXfcXeIPqR?`kZ@yxP zAF(IkHj9w(A%Nj5X&3Cra#9^s(Va{Kt5q25RLElyUipM00x5^#6z2BI*XS>TL_i8- zQ~zo`|0`Ns!|WPDr89cWv9)(trV}A4afUNf&8dPPOdhp%--mzc)$MG{RN3t%vh@ZYm0?KyzVo zZFd2=T?4X1Z*QeM0^Lw>^T;E`xD}T3>Sqm|4vOF3yVwQtw^IR{(43nuTvbj5OiZp! zbz_>8&s^wf_i(rpZ=(^erwb9kWlFyp zezLAA?l*@i_U;!Q{n<#s>msVDt{;Wyo;g4F0yz%l)YIjWf<4zn6tyRtYoCC5tHzG< zHs`LiMG;!OZHnkYjWe68k@j`)X7T*eyLIS*00c>pVQg4iN^^U`%^4Ld^+3%%x&E21 zFF3FC&>q3$yNLHj_=kH&QQxsL9Eo3Uc_f@!)-{N``ya8ze};b;Ge01d6=x|>Pip)X zASvsbwH*fFN)DqLqB+XmUPH5BKo{`+(q`gDy}6pbsF?)rZFTAq);~9K;olU;@U8Mg zNDov2SjRIK-t zV$B5A$H3<<@mtB+_?RR`RBo4cyMVNw2$_pE`G82+^B<9Zb1n7rN;koy|`4*oHD=n^dND{1Ao_>yd%!WpP zqIQqL+7(JzPP{@ul;|O**G>H7IaRBxF|=F~+HXC>(`l{$hUo(FbHuIJsKJ8+>lXLS zlj`LzBl!ge&~_=ziQ^wPNO|3Gj{hzD&G57rd*at1zq{`TGwGIfcHFjix2Gl%wN>~P zSGOsIvnKWF3uMhR$&}VYL2$}Ev66O;KYjOY=HFR;3s!81%IeYdJR0&TI3kfhd$@a@ zI;EXSzF@tx<~dSOXLYdu;?l1%Hn2g8@)np)KC4n0gMTy3zzmg|CgR2H%>0uFtxr%Lqd(qMeWh&D zSE|`#>Mu5h!8r*sE#0*3>A}JMBq$#|@BRBdd^ufDNJf(yQG^QwX#Xwo!1Au2oRp^tpVYFyzXm$@8SQg1^x}c^kELH3SEY*wW@3|D!Da z|1sL6V)Vl-frIgJeW}=Q#`=3*hQa~|%cB*YE&Li3({iywo~%WFfbi>SosejOg*{;n`$8~s;P!0q$p34eQnN3>3bO24f zY$ragD0r(`?$Zf*lIrYNTiuK>HGY5Eu}D(laa}bPJq=jAlfyny`u=YR7>v2Sod3tQ zQB70?G?0-VB{gL5dim*T%GPcPEmA5i zqG(D5cd1lSaG zR`jI8cLhfVL&6BS1;VJsP%a|FWJaO#pM0Zf+O&zo|2u4>8o&KEAd^~76g)S1aB8~E zz(&W5r@utGUNNCgqUHx3e`+)%*18_|(_4v{L_~u}y+F^&J6rNP<>yVcAN$UyP+NIB zvO>K3_JvVG?9}RvPaiR^nccTOT{r;h^gg;|Yms|uvm3?~qEu@xH{S3hO@RQ~8^|xO zFJ-^@bd^5{oF!0fmIUx(ioC@<)lvOZGNU3ERkCD(OWc1eZ)LJ2(Q(DP6L6-dV;-;s zt3~qGOA8A`MoYi--DUTA$P%0bQ$6gQdi0KcuQV=4#De99N!|PVZ>Ss^UfTR#1+|6>zdH2sEPKjsP{)q=WI3@PK^UCAxwke(&%j7dGiH` z?3Jv+#IWaSdzh@3uf_A;?T&-8w9^`4Ssv2}_nxsvIo#rAAXNiiQ|AHRu~Fgc(dl}> zEn41+Cwl9<(UTw=B^Az~D*=TY}tl$6i_09VPvBYJc^}nsJ1*tr8y+lqN#7 z#;T6SAXv>W`Ld1dv=(leRVmy5_+gae#ll%D*+HVkBi)x&yYtV*lP#a4s!-~Tzzk>O zj+xQ~9U$W>1Dx<4ct>v@k>RSZ_J_JgZC?KE4WB6a-ul}cuKWbzLqb;&$--wC(5Q(s zwh`D5;u%{cl~ZCYDteRhgOm6raG@1{JU4fo|mpYTo#4tr{9Ry z8Y!Zl+HoOv5h>)Z75Ws4GBe&5?`lW2O9gq z-{TecMJ)p6ZlV`SU_a(ukq&Z|9E(jIy>xS(}r z!e)>A0SFFiW8OdiXcWyc4Bdt0GZ&;HxMFP4Y_H#C;Ft=>##qwU(`#AqC|ve%pQj=8 z=*K;kUtS`r2SB{#)%?s4;R+NYJAY_a)Tu`?NQe#_eN+O3{yjJB&~Pw_1%c^r)|~g;wP2IB|njEC#*!iPqR}q*S1trzRHbg*xW4KeS=|to?19;-bd0u= zFx0H6^E~HE{V+ygR4#izpJl_?z~)4AWOb>DPJ;#L1ItYnw#)QuifT!;TrqP{tO%Yn z=4TtcC#u@Yfn4!a85d2ON zNAgpNs1(sd{ImC^VUskCG>>~5i56j?g<`;_&Ko{IN0!N@BLiYUpEzA00iI>N7m4}t zPHlRHc(NO=-+cC?(*(KQf2^dtp2yApb4zsi$G*z)5{c@94Ce;zCNrOf8>hLdB`W*51?+;L)`V8PN#!ZITAm`7I!&F- zs};kz?f4DMN(!qE<*(J74Usl7y)GKU1)pCt#19>pJ5P_`jY53`+f{uxk0%YdJ6Jz3 zs)QEWmP_>>y*zbUY}&EZuutWBDe;$0k|auAE*!Gr{(Xn@um@PDVIfhZkwi`?%TJbq zqJ`}Cn_-|)3VGeD_*FJ};+yRG@l0ngd2xiDdpm}Z$yWx-+MP7GeYAn>@eq0yK6&uB z&tm&2wk(pcJcsb%hzN^E>GhS1RcFE~`k;A)+Du2Q9)}0HS0!CnE!+g>L9Jg|Y-6}| zM_ro;lxnGc){Z}=th)H`{BRWEk5WZuLDj`jS|QZXC6?H1kj&N#Ni~_2m{l8=uqcgW z8Lsy4*qlx8=rGw4n#}l?mGqMR{J4eD0OcQUZr`^<1vW(`uvZR5XlMf%Uv}ZwAHz#@ z_C3FzQu7X1-j#-6(lNr@%hxcR(})%Y%ofLwpjpPWPvQS7vrt34&J;lZI!j1wNDMX~ zEC&1C23nL#CI>rJ+0Idn7O{r2u<#5PjvOUmEkt~$SXnFWmcPWdGc6P^n%xZMkWUon zLziqb0I!%qnng&m+*YD96mC&YY}w1~C)z1+qJ6>Ubt*<7sD&WQNcyQuMI$6^L>=&* z2nb%Xls@>P;XPqvJo<;k_;2CIqhy?k$NF^vmsf7_HBydMp7Z}}@67+9T>n2_5=vPM z*{gG;tb;^@;hdO?NV2aP`@UrC=BO-X%Mv1VD(M`%84P2Km@q@uF*6uUs2B`pEVF%` zKBx2fe!qXg=ZDYz!~MAKpYHp*9`EaYy{`BDzMk*A20k&4;rS1HC8pO}Q^SPCEY%~c z0dLigzZT2<kX4R9xG&Bigygq70!MB&o5Q-oU~(_n*kS-4vY6CTf-g*m;J9ypqSww zP|5?yWxMxjhXLa(rP@;lFX~zn+Yje<4@!>}*jm{gu?;T$ZC=&RlG|N1yj(Hf5^y3*9=ge*EDBG1ngI_1d&`FH%g zL*!GZ>CWF&s$@RQ2k{a^rlCj6r?Bk1G{HuTp+WjV0?r}uq@kOy*zR}KCko_Hp$ZQeIfw59Mw*4e+HRhL@vsD;K4FyvzTN3ESjE-tS+QM4}XW_4bCXqIxNi{!YzA0DC31opD46MUJi?@A(^}+QOdDWEaRp6_$gmDv)3OTHSj|* z{QVU_5?22E_#Zn0aN#MOf*3~oBI?J9vmYSU>Dgn<7L|=v9hdvDu2qv)loDg7Fs=mZ zuRv`gpbnBEQ!qu9gX)cDwh;USL_oY8?Se5%lqxLQRmk7T52r>r%pgtGSJL&uKT5mS zvAdnQ^^jE9H`Tac?DlN4f=^~)0bZKE)1n*q|IpLTQd|uD%_jWedk@JsflK(`q3E-R zERQ5Uubp?osXZrU!viSs*<^zKQtav; zndK0rz(_a}zKX2o%zvTei^dW?QhrF0?VK(TwaD^YkGo0=y43KKp}ZH54&0mMv;fsI zfzw0lzDfq9%nN-3I6SSE64fD7L8O`iXWh_#z)vApHnyPBkKUKOyjSZpNYQ!RPOY%^ zN-#8|q^`V1qij}&udVZ-7A_-IBwY&Dr$WC4W}E5$6BYQYF_&p}2nOhz$p^vDum2KH z&usf0I6|Nnt{jDaF6oN2gDomEQr3ATNwtnJ_Ycikkv3^BIq#@PN0(zFylLpOf7pE&4vD~@4}xIrRvoK^`xeKharB5^Rw*|$QRor6E%MNWLC97a(hBq9f?xkn}VsI zXiM8az4e|EJF+1=*MleSJwny(mD;rP-uE_W_!}SCCHoh`8(dfUj*nR;6y@SE<73-a z09db@&m{7@2Q>|H6?e^)Gx>qc4Ngido(hLz==17Z0c&iiUVkQ;(zTn9=v})1N(Om^ z2N0hp17~sgbnBaDbTRRhP2FB~UU8`c*JLL(Y^BgVX0QQ)Dmg}BFgk0D4xe+);I<{w znuCoRE*|Wr6+L6^q0p^Xl-`Es1Dw0<^d^m~6Ys!IPb43q_`ukGy@qc%7G`t8U+@l3 ztlMn2L6okUTL=W>9S~HtGTM1YAfkTRaX-A6G0fe6l8%>@l``nf^qx8lNcc?#HMT(- zI0n_jXOf{8Zg>2jX}tk6J_I}5ffrzwGhg4z4)1R2t4Wh&`0P;TcL1mbwKgQYSv%mP zZXkLOI;Ym{BjoK(NCd+63eO*;!0gUKF_W^MqNL^{djy^BW&*VWn$GrQ+e6~O5F%hU zT&HHHHyt@Rx&JC_M{6O7Fak~Y@8_u(`Xd-YHUSd!90Qm!z^=7Pgt+7FrP#$FgmoiY1*dm$se56e>r!yI6z$f*B^avYlM`m0`AEuD< z5Gw7D_8BZQjq4IgP<8c3n*hVskuJgLvZ>$yNul_@9K@z~sW~h%O}~j2S?kr-bM8Ro zC`+z!pg_@G{`jQ_pAvsNUHF_juWNN(^vzuojuuD*j!93u{2=3kbR# zih$kVG-MFz%2FO{p*YF`f3i5d0P%hVR^)tEWOP|!1vZgs^E9}Qs!5RuY*`zg`VdR` zD2=n11L*eA&O@h*=(_v^dKI%p+z|IAk!K)*td`BD!H=wT(aKt1I)A4ep6U$TT%Lnt zneXi44K-8B7$fvBP5EU%p!~uo`6;&^UK=C6=97_nSBQyw%?QzvVVv9+YAZagte(Dn zr2ZXfKkK3ez;P0Ml4-W!4QE9sebF1pY+7ucOW_0wryb!ueNcv#ChvH{N}DWiNnHfsjGze0ZK*ek z?IZhH&F76z)PTHsSI@xGX~g`1J5L{Ch35}VRvb9s9Dk6(5_CE2o+#|o8u!pVu=pJ_ zG#Zm1dH-TR79VZX)5Qy94A4T~6Fv9K)5go#l@~8WQfF#aHz@GS|Xdk9$oZY)qshzA7AaxDj z2=yq9MC6X7ggDC?aMU%*BHe4XfNLHZ{yPB360Aoj1)$9*u4nLAM?ET8<~7C2t4oiY ze&cm6zyPo`ztb;VmXFmvAAV3K8X8W4J?)!8E`byc?~-f+wJrC^n}sUtUs`j~@9eNJ zJG|fFlyn}vMD5pR%Ew*H`X^7B(GOgS8-n7|;7vT1J7MY0DRw9Xyc0<=RH^db7T&H3 z_lE^V{%k`!-DoM-9p%KZ)l^8McarGKn@(H(Fde^e!#MBN6~eI}WQc!dlFwZ*niH(P z&Q7}jLIT+M9PJu{ZltSqWJxT$H4WplBQi^RFRX{KcRn&j1DH2~rB~g9Q-n@kY$XxQ zF(bh^RuLef5|i)Gt^jORf7Q=e&&FPJBxh%|Q)EAVD8-vkKQLh5lgH^?>IgF0tx_dl zTMT>;aheTH`>5aO9?>mrrIpFx&BAQmU-4k-)bHmMqCtjk7Wjn8tEoN1BSyaID`?7- zmM!pV*)awza|*x=g#|5(R0=I#nM%CODv*IUq6i;4@rMQAk0;Q>B5hr(ZjU6xsii+V zdHuc9J*WX-LPEj4ZC`&o@T@4)Ls?8#7|zgBEM{Btx5z&2A>G8J{(lY z@w{JkPv1?Okf7t2O7%kpH64j;mT!%$m^XLplMRetm6K+x5dyK0NL*CaQRS?c@Fl+F zx)+u6mabY%Cg_s@pE&}|NaDyiiL=_~L-zAXC|9uXv#+_?m&C!WA+2yd3xrCZlyu?qp39 z`8H72{lN5P;whN&j?$?3HE*;%bebrj4wkxL6W`~GBQ7j!Z_UTt*4zzs31&Jqfl#*` zpnxo>e}bz1ogWeZsb3E^Pdh?U&rT;2Q@g^8^#tn-GMyX6pZ5f~M^s`d-)d%^wva?E z#~vAHX)-t8+JpJ_ieWgUMq1lhHUJHJM9Ny-_W(8#0X6dgOaRW?J74KqRMQdom{YSo zh_gT{49-=hAFhAGBispGSPa=hgVg7!1m{L#4!XuKqZoa@8t*Xc>tDN@k{Ak7`uY%+ zJzReTBRso&C2E2mv1esAIrU&k!aDq%Ai&P0-o5HHej+K9u#!u}L6CV0xz8b+XeP?Ne-; znz_F=v^%lH1PdRsOhVa3t;=(B38ZSKjxXZ@wy3mQ3nQ3bXjLOQ6gnp`9Z=mKn*y|- z({cp9dEOO!43IUBxTm*s9*t|9Z$Kc5Z*-D;SrLuiW${hnLnkuhIU8k-^jUwBL2OnD2Om37 zMqtFODoW)oE%rL$)y0cdZEElC|1qyzPPSc4HmT(Zir4c$qn4YWYL)YG!MBtCU^H>E znG6q|rK~go6)ryc-wtu{NsGR;)AuqGOq^E;sSrQ{We}jdog)j}dTgv#pM~w^R~y}4KyOd%B~#1 z7(+(;vF^nwcGo(X2=BkzM-{D(!@v@j%yWSVG33jY8%Nfx7@^;6FGmA_&3-TQ@jor( zemP-@vSLJEpse^cM&hQ|`6b0UnVo+oRB|H|RR$Sf)p#LBWpwn}kcQ*i-##qBY{=)(7S0a6 z!IE|7PHy@a#s2}C!usL`h6eYeBij`6Jv7a+`|QYXDxPhvdxiUh4U5(@?GC$8Np)As z>03OmHG9o(J+lhk#wnw|Laj8oSydb2E-EVa0j;X=zYk6OKn3IRl0 z^sh1R(*DTVD_vX&xaF_}=~Sj|^%=d-|;Ec6Y(!I{BDacLbyyLt|grNye;~w zb^d}&{VbH`ETQ9K6T>#L&^jjGDx2FmU@zTjkP-S`9AI5CRlyNM*q&iTj4X1E9{n{v z6lHUU+IF=bleC;PV4A0T-^nB2(?w$Uv}svEGZnrmR=%?m0tD~njt)9+(-gPfLCDmE zh$*7o(&NK@T-7_x8bi3=!mQw&u)NXb_~NiMjlNY|&K*xczJD@0&erZz4ca4c{W_s( zhWY4zdV^#1!?1^=4A%L^;Xklsvs(dHF&M!C`vE@J=F0;*SDG!*-8uBP-BZj7Le6}K zeb%YT=(qh{&~?Y)Nrh1Tc&wLVt+mOE--levhr`P@y*!ygNSYKk>{2_^*;6)ReQ?I1 zf29^<`!?&lHUG<`-T2Z+O$#Qw7>iaIEj7`I@;@#q65; zcHkmg6}Kx3RAu!k3uuey%)6K|V4g5rNN!~!km0^KK4c}u*wkkJ6a(KB_Y4@Teby?Z zWFjL~JNDRpg800W>3jxlJ+Z%!GE`1mEczOu|5kz$73HY#tatCccGh-kQ zwq~*Y;1pZt{)jAux!AKj!0h1M8B^WddKc@uHhFVayVtJyC~xC;Yu94a(o9qT0<*SI*{V(fR|5_o%Jw3w@=KJnXAK?Pynz&{A0{cb37P z2?6Y!d-o)7ADHv?+OQH0SI*P7&U}o7Q0g~N>)t=bz35St9Zh zfp$Qw(J*c?{^}poHjitqxFo1}MTzxh`&yd$bhGM{Kw5j4Yp&>aV_i9#a(CYW_3`He n2lCHw{ { + const stack = new cdk.Stack(); + deployStack(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check lambda function properties', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "testiotlambdadynamodbstackLambdaFunctionServiceRoleA709DBA2", + "Arn" + ] + }, + Runtime: "nodejs10.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", + DDB_TABLE_NAME: { + Ref: "testiotlambdadynamodbstackLambdaToDynamoDBDynamoTableE17E5733" + } + } + } + }); +}); + +test('check lambda function permission', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + "testiotlambdadynamodbstackLambdaFunctionF1BF28BF", + "Arn" + ] + }, + Principal: "iot.amazonaws.com", + SourceArn: { + "Fn::GetAtt": [ + "testiotlambdadynamodbstackIotToLambdaIotTopic74F5E3BB", + "Arn" + ] + } + }); +}); + +test('check iot lambda function role', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + 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" + } + ] + }); +}); + +test('check iot topic rule properties', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Lambda: { + FunctionArn: { + "Fn::GetAtt": [ + "testiotlambdadynamodbstackLambdaFunctionF1BF28BF", + "Arn" + ] + } + } + } + ], + Description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + RuleDisabled: false, + Sql: "SELECT * FROM 'connectedcar/dtc/#'" + } + }); + +}); + +test('check dynamo table properties', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::DynamoDB::Table', { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + } + ], + BillingMode: "PAY_PER_REQUEST", + SSESpecification: { + SSEEnabled: true + } + }); +}); + +test('check lambda function policy ', () => { + const stack = new cdk.Stack(); + + deployStack(stack); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + Effect: "Allow", + Resource: [ + { + "Fn::GetAtt": [ + "testiotlambdadynamodbstackLambdaToDynamoDBDynamoTableE17E5733", + "Arn" + ] + }, + { + Ref: "AWS::NoValue" + } + ] + } + ], + Version: "2012-10-17" + } + }); + +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: IotToLambdaToDynamoDB = deployStack(stack); + + expect(construct.lambdaFunction()).toBeDefined(); + expect(construct.dynamoTable()).toBeDefined(); + expect(construct.iotTopicRule()).toBeDefined(); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: IotToLambdaToDynamoDBProps = { + deployLambda: false, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } + }; + + try { + new IotToLambdaToDynamoDB(stack, 'test-iot-lambda-integration', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda-dynamodb/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/aws-iot-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-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-iot-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-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/aws-iot-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-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-iot-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md new file mode 100644 index 000000000..9bdbbf5b3 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/README.md @@ -0,0 +1,85 @@ +# aws-iot-lambda 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/aws-iot-lambda/| +|:-------------|:-------------| +

+ +| **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`| + +This AWS Solutions Konstruk 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 props: IotToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } +}; + +new IotToLambda(stack, 'test-iot-lambda-integration', props); +``` + +## Initializer + +``` text +new IotToLambda(scope: Construct, id: string, props: IotToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`IotToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new lambda function or use an existing lambda function| +|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| +|iotTopicRuleProps?|[`iot.CfnTopicRuleProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRuleProps.html)|User provided CfnTopicRuleProps to override the defaults| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|iotTopicRule()|[`iot.CfnTopicRule`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iot.CfnTopicRule.html)|Retruns 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)|Retruns an instance of lambda.Function created by the construct| + +## 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-konstruk/aws-iot-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..2b9c6c7adf7bd4dcedf627c2f80a20cb60f25c78 GIT binary patch literal 50692 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
W%A=W# zZ)lXJs^m(cYrt9|oW+QMNlqG8QHYEQtOqFThHB0**_sa)XHpG)3C&hvsS5<<0FE`Z zEcv%Xlo!NSQKB~GAu=20G8yKEbDcNHiZAWAv?QJ+*XMG}y`H3CoE_Zjv{CmK{p(wC zI@`X8{0IUsdT4rMO@K0c{DJ$z+}vsZ<9$Il)jr$5y;(z~;SRw|k{vfg@algYQS7j| z+dX`3UEG8N289J645=V=~fa}<0Cc*VI16Jx3tl2*HK6j< zE1x{Ohx%nrvi6p5u9ENKY(k;kD8L6?xE4-h+$JeJE{MBBr7yBxiRbIgmiXIOr({Iq z3maK9IaV;8mW8Z|qi@FRz@ol%TmwF5gOQG(+gy&y{tux>h5{NxV>&1&XoBH=-n(w^ zE>oE6Xe^?1cTUbzoh18Nx#SyYS=^-)yvnWj*898VnJZ{--q>(O81f1bwwl1e^>sBz zliS%Hsatf&u`lS7Tz581I~uFN?~=MAac$P4o^gHV(nP6K6E~2W8U_2oa^6I%xqK#S z3=H@C7hdZO{0_;>Geg;^K8Bd(=WFlq@RoRBs|g??Tp6Q&sma+=Wg|z*tt=9jO*Lely0gulVpAwc zc3j}?P->bV&Uvb|KZkhR!JNVE3sg(?A)YjpF=2pUk!9T!m(LUO1ZZu?RVJa|{*8N_ z*S=WZ>yspH5?|7;s5S$pLw2725w_e={}f>^SODV7TCitHcFrFDk~dSXzK8Rr-H)OD z7|9kqa%6{V7yG7MEln*i%GL`aRfbrz{8WNh{L_>p-c{5{s`_LE*&1vAqqno=Xgw=! znr>_d`=tOblJE{qg}*k$A&Ze-;1KtjayNAzuBAw`$iR&O{Q#9z&bS{RGg+t7hFyLI zz2<*h@-aGGdF zumx_)ho``N4T~;1onoa{l)>en+-&vQN@6(t~W`iove8681c0AU%Wh zVxZFb+z-=qUO-U*@K=4&m}80v+^kZxg;BPSi%NKEdxx^Q#ICI{v(MhAVw=$J{!lWM zQ^8Jz7hk97{JbR{Kw2&kaZ#?F!XUISo?+=2bx3f>5dVjab30eSCNxG%TpCsIxx_e7E0SBcVOf0v ziWg4Md<1v!&)_O=ZcWGF<1FbtR&k{En2Fxm)>`{Fhbez!fY@jwFimKizQ)C3;~7PG z0QeN}a=htE(QV|ElET?wMFqT>oCI? zcT^J~wlu}+dE_0NI{9V+ZJrvk%GM z04sxY$)~>>q1+fx@#`-OiuWGxWPTaol(&1Mq6y>XC<9klz)8fhai$&qUA&zf0y zN0ZILZy51c5%?70(;)+sy&^Tvq10@)Rf8v#n1X-}`_Gi-gNL5)io_i~xkNv0i|eLN zt$RHZNPtOq67g~`B+u0=GE%gf^OQ}e@i9uCx(RKp3;s>T%Cx$5>#}$X@*zqSX z1@`LzBE$tp3Zo~%?3?qxB>^26Tt!Rw_s}cWaFBQ9M+eZ#Z3YEzXp6(`#B`x3d7MW* ze^shn2@h6AiBACsoI-5ATjEX890Ff_4*a0bO$z2_&3Alk=mT-1ALZOLUal}26^Gv% zsR|AUbV#6$iyw=r*_Wm|;CQRPlYT~R{LZPtSid-r_MkF3%QbGpd=b6xOv>Nky%wWN z5zu)X3+$gd!sD*Qb2u}Jd-GMR8AHDeDyh1fwLQzMD$RksR*y;Aet2chEP7Dx>h9}( zXtSaxOM+=T^x}FS7LwjJu-&I(U1qrWHtVfMad9JbfvXvKErisu_$vH9LHQVOr6
    Q-hzB$~g{rj1liF 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 L{-#3CG~j;$x)5Ok literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts new file mode 100644 index 000000000..b9a61de43 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/lib/index.ts @@ -0,0 +1,111 @@ +/** + * 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 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'; + +/** + * @summary The properties for the IotToLambda class. + */ +export interface IotToLambdaProps { + /** + * Whether to create a new lambda function or use an existing lambda function. + * If set to false, you must provide a lambda function object as `existingObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * 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 lambdaFunctionProps?: lambda.FunctionProps, + /** + * User provided CfnTopicRuleProps to override the defaults + * + * @default - None + */ + readonly iotTopicRuleProps: iot.CfnTopicRuleProps +} + +export class IotToLambda extends Construct { + private fn: lambda.Function; + private topic: iot.CfnTopicRule; + + /** + * @summary Constructs a new instance of the IotToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {IotToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: IotToLambdaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + const defaultIotTopicProps = defaults.DefaultCfnTopicRuleProps([{ + lambda: { + functionArn: this.fn.functionArn + } + }]); + const iotTopicProps = overrideProps(defaultIotTopicProps, props.iotTopicRuleProps, true); + + // Create the IoT topic rule + this.topic = new iot.CfnTopicRule(this, 'IotTopic', iotTopicProps); + + this.fn.addPermission("LambdaInvokePermission", { + principal: new iam.ServicePrincipal('iot.amazonaws.com'), + sourceArn: this.topic.attrArn + }); + } + + /** + * @summary Retruns 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 Retruns 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-konstruk/aws-iot-lambda/package.json new file mode 100644 index 000000000..a34c9bb12 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/package.json @@ -0,0 +1,79 @@ +{ + "name": "@aws-solutions-konstruk/aws-iot-lambda", + "version": "0.8.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" + }, + "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.iotlambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "iotlambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.IotLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-iot": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-iot": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-iam": "~1.25.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap new file mode 100644 index 000000000..88383f6cf --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/__snapshots__/iot-lambda.test.js.snap @@ -0,0 +1,183 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test IotToLambda default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionLambdaInvokePermissionC135C9F1": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "iot.amazonaws.com", + "SourceArn": Object { + "Fn::GetAtt": Array [ + "testiotlambdaintegrationIotTopic18B6A735", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testiotlambdaintegrationIotTopic18B6A735": Object { + "Properties": Object { + "TopicRulePayload": Object { + "Actions": Array [ + Object { + "Lambda": Object { + "FunctionArn": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + }, + }, + ], + "Description": "Processing of DTC messages from the AWS Connected Vehicle Solution.", + "RuleDisabled": false, + "Sql": "SELECT * FROM 'connectedcar/dtc/#'", + }, + }, + "Type": "AWS::IoT::TopicRule", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json new file mode 100644 index 000000000..046adef40 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.expected.json @@ -0,0 +1,179 @@ +{ + "Resources": { + "testiotlambdaintegrationIotTopic18B6A735": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Lambda": { + "FunctionArn": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + } + ], + "Description": "Processing of DTC messages from the AWS Connected Vehicle Solution.", + "RuleDisabled": false, + "Sql": "SELECT * FROM 'connectedcar/dtc/#'" + } + } + }, + "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": "nodejs10.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." + } + ] + } + } + }, + "LambdaFunctionLambdaInvokePermissionC135C9F1": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "iot.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "testiotlambdaintegrationIotTopic18B6A735", + "Arn" + ] + } + } + } + }, + "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-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts new file mode 100644 index 000000000..49b7cedd8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-new-func.ts @@ -0,0 +1,40 @@ +/** + * 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 { IotToLambda, IotToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +const app = new App(); +const stack = new Stack(app, 'test-iot-lambda-stack'); + +const props: IotToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } +}; + +new IotToLambda(stack, 'test-iot-lambda-integration', props); +app.synth(); 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-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json new file mode 100644 index 000000000..d91fca779 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.expected.json @@ -0,0 +1,179 @@ +{ + "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": "nodejs10.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." + } + ] + } + } + }, + "LambdaFunctionLambdaInvokePermissionC135C9F1": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "iot.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "testiotlambdaintegrationIotTopic18B6A735", + "Arn" + ] + } + } + }, + "testiotlambdaintegrationIotTopic18B6A735": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "Lambda": { + "FunctionArn": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + } + ], + "Description": "Processing of DTC messages from the AWS Connected Vehicle Solution.", + "RuleDisabled": false, + "Sql": "SELECT * FROM 'connectedcar/dtc/#'" + } + } + } + }, + "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-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts new file mode 100644 index 000000000..c6f74df93 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/integ.iot-lambda-use-existing-func.ts @@ -0,0 +1,49 @@ +/** + * 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 { IotToLambda, IotToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-konstruk/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-iot-lambda-stack'); + +const lambdaFunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) +}; + +const func = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + +// Definitions +const props: IotToLambdaProps = { + deployLambda: false, + existingLambdaObj: func, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } +}; + +new IotToLambda(stack, 'test-iot-lambda-integration', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts new file mode 100644 index 000000000..c975f3658 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/iot-lambda.test.ts @@ -0,0 +1,319 @@ +/** + * 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 { 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'; + +function deployNewFunc(stack: cdk.Stack) { + const props: IotToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } + }; + + return new IotToLambda(stack, 'test-iot-lambda-integration', props); +} + +function useExistingFunc(stack: cdk.Stack) { + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const props: IotToLambdaProps = { + deployLambda: false, + existingLambdaObj: new lambda.Function(stack, 'MyExistingFunction', lambdaFunctionProps), + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } + }; + + return new IotToLambda(stack, 'test-iot-lambda-integration', props); +} + +test('snapshot test IotToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check lambda function properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs10.x" + }); +}); + +test('check lambda function permission for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + Principal: "iot.amazonaws.com", + SourceArn: { + "Fn::GetAtt": [ + "testiotlambdaintegrationIotTopic18B6A735", + "Arn" + ] + } + }); +}); + +test('check iot lambda function role for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + 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" + } + ] + }); +}); + +test('check iot topic rule properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Lambda: { + FunctionArn: { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + } + ], + Description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + RuleDisabled: false, + Sql: "SELECT * FROM 'connectedcar/dtc/#'" + } + }); +}); + +test('check lambda function properties for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "MyExistingFunctionServiceRoleF9E14BFD", + "Arn" + ] + }, + Runtime: "python3.6" + }); +}); + +test('check lambda function permissions for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + "MyExistingFunction4D772515", + "Arn" + ] + }, + Principal: "iot.amazonaws.com", + SourceArn: { + "Fn::GetAtt": [ + "testiotlambdaintegrationIotTopic18B6A735", + "Arn" + ] + } + }); +}); + +test('check iot lambda function role for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "lambda.amazonaws.com" + } + } + ], + Version: "2012-10-17" + }, + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + Ref: "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + }); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: IotToLambda = deployNewFunc(stack); + + expect(construct.iotTopicRule()).toBeInstanceOf(iot.CfnTopicRule); + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: IotToLambdaProps = { + deployLambda: false, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } + }; + + try { + new IotToLambda(stack, 'test-iot-lambda-integration', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +test('check deploy = true and no prop', () => { + const stack = new cdk.Stack(); + + const props: IotToLambdaProps = { + deployLambda: true, + iotTopicRuleProps: { + topicRulePayload: { + ruleDisabled: false, + description: "Processing of DTC messages from the AWS Connected Vehicle Solution.", + sql: "SELECT * FROM 'connectedcar/dtc/#'", + actions: [] + } + } + }; + + try { + new IotToLambda(stack, 'test-iot-lambda-integration', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-iot-lambda/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/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.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-kinesisfirehose-s3-and-kinesisanalytics/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-kinesisfirehose-s3-and-kinesisanalytics/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/.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-kinesisfirehose-s3-and-kinesisanalytics/README.md b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md new file mode 100644 index 000000000..c5ab3a024 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/README.md @@ -0,0 +1,102 @@ +# aws-kinesisfirehose-s3-and-kinesisanalytics 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/aws-kinesisfirehose-s3-and-kinesisanalytics/| +|:-------------|:-------------| +
    + +| **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`| + +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. + +Here is a minimal deployable pattern definition: + +``` javascript +const { KinesisFirehoseToAnalyticsAndS3 } = require('@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics'); + +new KinesisFirehoseToAnalyticsAndS3(stack, 'FirehoseToS3AndAnalyticsPattern', { + kinesisAnalyticsProps: { + inputs: [{ + inputSchema: { + recordColumns: [{ + name: 'ticker_symbol', + sqlType: 'VARCHAR(4)', + mapping: '$.ticker_symbol' + }, { + name: 'sector', + sqlType: 'VARCHAR(16)', + mapping: '$.sector' + }, { + name: 'change', + sqlType: 'REAL', + mapping: '$.change' + }, { + name: 'price', + sqlType: 'REAL', + mapping: '$.price' + }], + recordFormat: { + recordFormatType: 'JSON' + }, + recordEncoding: 'UTF-8' + }, + namePrefix: 'SOURCE_SQL_STREAM' + }] + } +}); + +``` + +## Initializer + +``` text +new KinesisFirehoseToAnalyticsAndS3(scope: Construct, id: string, props: KinesisFirehoseToAnalyticsAndS3Props); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`KinesisFirehoseToAnalyticsAndS3Props`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|kinesisFirehoseProps?|[`kinesisFirehose.CfnDeliveryStreamProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStreamProps.html)|Optional user-provided props to override the default props for the Kinesis Firehose delivery stream.| +|kinesisAnalyticsProps?|[`kinesisAnalytics.CfnApplicationProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisanalytics.CfnApplicationProps.html)|Optional user-provided props to override the default props for the Kinesis Analytics application.| +|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| + +## Pattern Properties + +| **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.| + +## 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-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..9f9e472d87360ba4a06b8496c403448a98b61774 GIT binary patch literal 120293 zcmaI8c{tSV-#%V$_Z>+^p%PM22ua8~l_Y!i-Gq>3j3vWhOp7I3l093IeHrVFU9!d4 zw_yg8ZLDJ&!wfTqpZod!e#h~AzaP)@hl6n(j^lm3uj}Z+;shV}zh5U^ zvPX{o+;Mk3z`QM<;>lFnOqir6LT->-J94CY$GEzx8FUuA(ZnE z&yumVMkjj&Z<5Zn50WbdET{E{Slpst-DSB>xq1EOLvAB2k)nE|_Njtwylg3L=2lWF z_7-Lwh-twTQCi$p*V85hc9;FjVARKs4^Ob1e{+KM+KH2F|Lwo-V2Oj2BdqWqGYv-8SW}>Ie4#9DH^4%sGai~3CPsxB7b02?Jxdg z9rVRXCR236zQ6lpE;K+2YbiRRDKCp9`JqMC$r88~R-J`6)_cr|(CV3IMu&Q$pt&s& zoboPi+ecSF?MH+0$7{zvSRdEhq@FEVGc#-+2ZvHkj}aAF8y6O=!)f#BeWV?ldIJ*~ z^Tm}*R?0tyWGWPzq;hiNx#vUNsHA&&!*v8qyZ5Qj1Axf0t)YsB!80 z`OZYKgwMMF=crDZh9|zNz5!!lF_0FRSga^`y7EM)_%y}p`$R~Ha_WYh=t9`(Rde+_ zbh{38U1i8`hQwzoWz=B@#aEj*J=%y=<{`~d!%{VrB}LBy56Z`RxYK-*McR?Rn4@U zntN_)NgyOJ<)Q)=GmXh5=mhg)Oq$N^(dvB+Fy@>Brgp`^jO$M{@|gwm)e$SzQ89S! z(!O}}y@L4})1CG~f$ljq+Sf%P-x-sarLia8&0}}DIRB5`|F47aP)P4bGe-*EqjiuPHdvt{C4p{`GH$DT+(qXy7i9ve-3xwk8C+i3!Y@{#O1aN{@=&+-v_aQqyZIY(l!jhm4Ue12oh_=emQ5pH+SCVyCz8PMg6uVD$hgqX z*9^}=x@9rV(4nXsmy0MVP-)G6O38Z2m1{Js`5{|1f=HtarjrT$sv4>^vIn<==Bdy9 z@Y^X2yYNrRg*ILNLvz;iT`on^Buny6Wkp4OrZWA{180n zmus-}CE&5?1VWW^;$Fn$s|yU8P%uKbww(cCyLNl_>{aCdRr>$6`ES^seO=4>DrUmG zp1tlN1Jho|6|yQV)=K1vwsw#vn!#w4Z| z@*UV;@?gRgdYm3sC>h^1h2;)vdLXedP{BmucXrUOVmDR&zAD`G*>0Hg}&R3fk~}{#5p3) zH`pxiOYz*mWpIn3^j2Wg7WC8Jcktdt1E{-H&|q0D>f(;v;GeecznoB+*u+>F*`w-7 z8|Gp=rp8syr;cSmy()74gg=Ltm0dIX$}}(9#XilbFf{ejyp-K za&LWUEaP&MZfGO0voQ|S7mzJ+Gpt)MyKR-9JvZ5Bx18_eJ<6gZt`p%w`?Cz5HdQ^4mwl>KnLj_uO&E znmo2YlQLohj7}@9EXWvdT}GUTZtrdF5_`HQ5RpT58S&no|YU*yGFB!@Ai zDNv{43PH(V>(zVJ`-o4Ci^_fyL$9SY>e%FaB>;mG_PXqDi@_eg_-W}zTmu4R1@(K* zC>9scmwFFMjaw+$e$e7DmNr=3sif6u5E)}9eN{Dojj^f;kxpI4Oe1Y%ORX0C`};L7 zC!*-vSCAu@okrQxgO~a(B#ySYmiwmQe`5LD(e#HQi}f894@$L6i_DTN{M^}YkbCou zL-Jl*4o%MXkA90+3j1lNIgo^6S(khy*(sZMn#((}=2lucAoY|zB4=#b>pgkG0e_yq z+756bAXw2ddwgwoAA6R|aM)CllK1L?_T0e2F84Lr>rpXl=66W7Tox##SB~)iNh$tc zP+8S`80ZiQRB~mvXb4-*Nhz_I;|hW*59rSLe}C;~?IZ`_ThqPkOGF3D@#7jk6*OPu zU0>*2D*dH|(g^QT3xL5z3`ux{( zo?s<}K8$n>nI7rB`8w^h>Gc`nw@=6=o=CPC3Mcxv%_rLH^5N%v;t@+4|QN}G_= zjmd8w4_tj6l`<(W7&0rSK0%xOL*!a`pI6!KF2K1>F}InI_Jx*VhaW%6wMX|VZq^RS zZ^a~(u4z@au`7D?xg{&Ix_oV&Kj`jf&?#Z@%06l|qOj zp@<`b?{J^Q-fgTFTfpvHv}IjGN)$AAlQ%DXd_Uw2{m$9-=Vn%ek#1+Y@pj>sw&#u$ zdPRHj)k}2NA`a$}56#5W=+3&uknfTcrng{$@qe~l1Ye5JdL(Se5{1>AgBPJsyz<51 z>fWLrtjDzy16J*89FN3nRsZ^ZJ-yksZlpa?lHEn*{zQvKSY4i02byq_YBPkF?OPNi zSu5%UwHReYKko^W%7+F7guYPdOVZB8prEe492{KIJqRkAyIEo|(WbzJ3-KlSS>x80 zY-5S)9ZQWQgG@xXdk)=akUoq_%{DR>{W~T+Ew>?fs|ky|S!w3!G$7dSl=x<~l(p{L z0|~|XV?a3d97}>0=I70-`WNQ6{6!@HmS45I#Zz|EOXE#9PV=2?Y1M1{QvQaAqT_e= zlMY_)==>TDiCwKPEB)G`q~+c#bB5jXN>xPdY^5v_xYNCy!A(&yUL+TYtdE-j*TsLI zHS6r`E(B(TpBX(cCijeqv=?RY2t=5{mXe||gm(@nzwE_+eITpEa!ehk>aa-Z=M9O} z_VScmMAPx-YM9u&J7wEae0>+Q0rTcTj+MIbh`S?Ss)IKaoLkP=>q2l|@ppJ+v(cUM z_fsbGv$KBe>-pFUFdwT;X>Wpd!3dkZ z6rsFStLZdTJjV97(GQD_-b?IGBTa6KU+T5U2N!bEs(vY;X@y&oWRE5;m<#btqNE$v zSKQKSL5_RnacMfWR(!P}uy=LG_y z!CA7ek_&aI@(7>N)6aF+s$0)rbaBzVd0b&X{NZ<}$ecTWqiF=O4PjU;jZDY-)M|;_ z&CM65L_4I3->U=a8Lw{LS8pO(6*s*tfYzZ)AH#@pP9p{vi39CrPkcY*-d194-&ZrY z2>l4Y)up0OvL|~1gBG9n+hGXa4!A_L*UD_d&%l8`xn6?HrrLnfp40FLErd&QBz{dd zqD=?6wn*5W zMH0dt+R~Uu$5L7O`fvhH@EOgd*2<@ z%zereq^> zPhOsOZ&#Iml`a&5K(4+`soC+zjZYAab^E3Z(t&hFx3h0RNmN zns?lN$He7N0N#dbqvCw)&Hwm@`B+3v>OZ->ON_RW3e5`?(dgUYt_WVVi?pvVW6iq3 zDy8Mf-8C;+bcgk>(`|bh_lCSJYqLlP%l2{Ebm`5>5hT1l7fUTr$ti0^D{A|URMyzr zT4O6~L3Io$Xx&k0MGdVWzW6xU}#3?ul$?xi4#J}&z04>cF@lJS)((u1=o3#uZJ)9 z9T7N}r<&J}D?|0KucehM&=y9h$EMZhA8e&S_1GnSqppUpa$Z+Btds702ujcOCCN(v zCJR3l&xGDCV6J2%Qh2c+!SpT2gl|iOBnW>mQ88?4B+aFUI2iC6N8Ofok2oZ}ClXSc z^CW@(2~NN4a!OrdZUnve_H;+m6WZ_6Hgn7e7v~PxgoiU>c-0$D89m6y;wCV_%K1`C zqt;2}kgT1ebcnq_9*gi_2+wJ^`d=nZHMTl7(Yd>S-)A@d-2C0JbL{3_9I%2Dh{n+R z+YdOsMSe_`E1q?*fTkO=t2|{CdwmX|Si(sh*uSMioN-<#%CyV=13D=ijB2786DMyW zK)2N3zUfyV%y%Y3JVEQyN+o5j>-C_~8=H=mje(Ot=N)kL4r#aa*7=hlk`vhVlpW(N zNnN0HvvmO2wg8*D$-Q>z%B|QG)z5Y602p?EI_<|o!qW5Z3jv-Ymo{>Xq#;kE%CV&wpQWi3}Co zqu=lN;UJ?g^C8CG){^Zc#+=tWP2Ua>5ZkGa07(4^c4i4iQ~FleLXV+Gf@~|+@4D50 zn(S-qO4!~Bt>i}SgNoCiR91;a;HS|6tMBLdlmnB&O`3{2skezen)isE&4}F+$PQ-Trg2r_uu9KUF<(B| zW6yx*zI-@9s|}M?e|3k;u#yK@=r*Cy|V{}x8t-s8DpXLPz&2z9mE%;4^# z39(qnr9XHU7F9&}-Q1vePUn7bBm{L&QSNAy3c5JJ$8qJvH_piYk} zxCznA538|g>`%}3krO5d;0_O)n{62}d?@SfvO&e|*x}{Ytr6r4Al+ygq59}jbj$+o z&DJo~eXKOFIL;?X0=v=sLL+8=DN!xTJX7iMJbz{_B+=+mJ616+@QS#MvgyA|&^`k{ zZV4aXa9HNstrD@xY_Ft-d&5eGntt9HmYCQYe{Rz1%@_VlPV8J>5wuCO2nJ@rLtZIk z)S_$_CX*U*E-hqp+&2le&HN~YfAVS&*`K}S=~x27y41cu4(@5d*!KrcoMrYiN@CzQ zw#$KVGwaDZyHl0IXLEOTE&bMKE=<0x?Z*^17q@I7E?!tFo6hn$f6B3BH^SqR+z3IA zN02=j%trJ0U;8o#%eN<5iB~qe!X``=h(3M)s+{;VUoO3JT`P$BXRe<2XbarGY2G=m2|s*s(>&}k&*|he5zjqOafdIM zvIn?rRZvpuM0@)%%yIAxcB5s^a!-1ngdgTbdHPJC8RybdcefzHw3brDHC*pvR1xlj zo$^Rvsi5Ukq6ScYRTZtU0H!Aaz!9?tbAqV;mL@0kg3!kLhw=LkwekXYsa2Ors_uo& z3(Dn7bk*Ox^X_KH@+2_?4#B1Ak)j zdI-Ii+9{#1lYza&@or{VqyAe98axplxZ-r*5aF}_R#Bmqf5SQ}4n}Wi@tufM$&>V$ znZ&$1U!SPZx3ft*jeddiJ1}IC0A9Wq(&Sp1WhS*|xNfM1$X?zcJWH|cI*<`Mq+LyM z2HX_Tpmo7S0xPdr~TE=u<=+-Tig<3BCPiCZBjKmTmm z8M9#O?!6FRByB%M102>11mhy^SS}{M!mJnM5bC%6*(B|J6@THJFDLCZ8|?7x?0mGz zO|{mqQ3dQ^A#Eg8gk&kuO~(b~uH3pDNz4Z_I0g%(-KVxp&$*_zE^=T;pvq-)Ve(Ve ztutFK61aF2yijk8H?KG*Vt~{fWNZmwT-3#5vgnvgLJt|9&l;Qe`>N{kw`Uu#{!EIh zg#3H7T%P@NOm*w~9!)V{SP3kt0u!^s6P+sC<=8Wqqz3Z-ERlIP1PP`7A703uI|;g( zC>AC%dGKA@b$t*pdO`oAAUG(5B7Lf|zv>Am9@+gG1~LO0?~GRG>=PsJ*oi$=_trez zSwoV}9kvkn2fFtLE&~rgKz{qBpk%Y?zmPzwa z(vy)se?H{dCBaQf1_1gOTsSPgl+^zgLVmr>rQ|xoxii!!Sp5lNz3X8jdmTFP_)VP3 z;N#d!$1IKz@doyEqEkKL`{VCbx#{^LT)F@`Gc(c3Ue#5XE1SY1eas{Y4^LX?YC%2! zlG_E}Y5RHG*k-DTSoe;|o%rH+->ab+Ym7hlxe&EFuDlnS5ZZQ9AForhGj+iix=1*{ zGaB<-X9_D{;i+l-l?ni`5QR<35)xrWbHn7{ESwlJe4Fxe(2WV<;gZMSy@jV81 zL)sU^IWdsQNOSJ(;%HB`t=Mi#9BNP|a&aZbZ zK1iYE-;UD)hUN9!wG^Oe-~FVv%@9}%HLvePc>fZXEYp${Fi%mag|#w}$mLtWQO{j6 z;1$D;iJ~e9g709En>p3EdTAKM+C@RK*ho7i&eCLT?Fl0)*lO2ARW&(#U3MxV!SUIq8UjQr{o3Gx&HpbPZ9{ z+{#o?g{)2s5KH-B(b-Pl8>!e?V-{29nwIS@mS12B-S#K|Yv-<{_8^RLTkUbcz_TN@ z=c!ldaZ(C^Rog})Ne@q*2IZS+6>qi9-C7dok>w+Ft)(Hara{xs6MTbicVHUcE|+*s zZw*O%fmP2kM_cKf9nY4zVA8K2NP9p{)Ji&@ZYRwy(C_UjT`4pTbT)uF@i9DAhRz-` zH5-`d zUEU8yr$yRt8NlPDlUlhD z9lAI@SPE5i1Y zv$YTxoU!K zd>i4WQT*+o4Rh#-+x`=Tl%$GCUct2xD~Utt2YDFt*fVhGJdG`D2QSqIIpKDv_P@QRFL)8?05_TQ-X$6VE!f znz<=P9VM^Kv5TI-U?coyX>)FN9kTKE{Vj3VC}?2)Z(in7PV2?#FkRo*vyG@lvMM9kQpPG~dF}drIfINA zs$%2B;Q4Xk4#XpuXD>{ZfDOw{4qG{{uXCC>vMY1i^anUKW6UKDG!!b|Pdga{7lp0AO<4-;~MBiUR1piAi z`-31oZq)S_Oa(ju^Xy zV_4PQ0@m+VT0f&uHORjiYlNZc$1l$q_Ny4#_zDeIRaDgvC2LQ_T=wn=EJLNF_O zkv`kAGxx`YXltEI;HLnTg8AhDMA*^Ev0CBcIHnRa&OdAGe^PdS$>@$meDRn<0ar`; z%e>zrB<`Kl79T(yAj)5xSSe^{9*DUTZL#RP-8)14fm56Gr01eRNs1!H&=)Ud#QJaL zadstpD6a1`|23Gf_NBYN?zTB+5Q9@2;P~Mkm#2Zm@9TD%fa^9FxBv9oLw0H$;FMus z7iLoKt)nq9z#F$;$oQ}M1GlxpsP*H^%(S$^1spaaZCjbm#2@ZbUbk!T&>8)^+4Lr9iv8N{fq{?DjstglSNyq0V4-*?lrze~I|T>s(@ z%;k9f1=p`C=EFTGZ&OubyEQ`EbeLaEb|hl68MzQjc60Tz`Mq%(4w8npgW38b<~V}3 z!<~jMu?H)?g>~yLRi}|kv}YjO=T&d^TEbkD*9WFH6*w^XrK{P}M_fPQMmKgV_7s+U z)N4_}yTwSK-Bg!#vivGkIpvjp?Z=Qm#mHs#Y*+r4Kl2bOM{8wSYxkaD!ybAQGqBeQ zc2zj|MY4VuNhu8(k9%6iu*oRJKhVqQ6+mb4&knwvLIw2cAMq0iQ>KBV07kSBIT6F{$r%%BdPl^GiNX`~9_ zq$hrVi3WC2p;QuolbE&->iq&90MVPfM6WF4`ENE``_!#^&85pT_TD{uM+!j2gP_~- z$6(NZB~31wI;87McuQ@Ir7T>1gRpJ8vMn7KUkzo%OavU6Nol%u z7~zo9U21(K`YmRGFoQ7P#$7$>SGbnBc;~bBvICxBRc1TFa|ql=K}wvW5On!g;z0LD z7r3c)!>)%vIkm{H-|4j(-dv3#S)DB@7y`+5OW=<>8Ng}4+9~(r#>M{xfVX?shQQ2m zZ}Vu!5wvcl&u}m7pSK3cPxTIP`}PLNERC7cJLVZUvqb!@tma%vCEHo$fvNmFZxx$2FZN|&8zbLg)6|U+^POEPwx!18}PjhcUj2K4k;~OPwj+z z4iF@|O>^A>s-(mTk}5o2GE$vr)5=~;SCKBxoc#W##Q6nX?V3?fp3T6vQQW&3`BE9h z*E%>FAIa_1?EWldW5K)|B`q;pXUYhQYRIf(_M8J-XaD&m*t**059T|Vqu@4$Lj08J z2NKOKw0NNl*-F*~n_2%lBCHcwTMb`Zo&A}N2=u}+*1>KG%@L)9p^~-kv<}4qMDlAp zA*G1yYLR;NLM7UcJtT|2^b%R7lAsK(ehtwE}K(~m4xf?#@%9lS^ z_o$t+>4NmXhk<|RT`S5$n=Auf%`Y7pEh={N1ks~c6cXwYa>v=MG(*CU)RraGm~7v> zxeD$3Uq+<}*8M9P>}I0+d7^~#!9;EIXOLej5hOHe!J~|_^vEfy@DrrV-SEN5r5H!^vET1 ztv`ORZQ(9;c=^WRpW!UnX#1w=v+4ZFq5w-%K)q{`TBE^K{{vWpNmh*I5%(SVAH;{K zH`~SMt2QR3`o_D#d2j2l%WtIX@Kpy(2~Am9`yZr%W~Oi+PU}lr%KLMJFKdG5Z^<)P z9~tk>Z1hf=sHWw%eoN{5?H9qUvO4Sj$`vyJX_JNp@nQ9+1HQtj%621XTNw$Pbq%*N zANknA_8$~$=mV-1pY{DKM`Ha3;ijW_jWG)>vOd$@&n)?7dRJUF`ohb>M?Fk16kotB z>!wRT7##eHEm?0&*pN?CY6bsV;ctlwxO~Z7R7*4(n(^1Gi9R~7QEJVA{6rScSJ^gvc=KH$BMpNJh=im8j zL52p&E#H1{u7Y^;R~Su4#IjROvGginxSaXTyw=g&4w1xsDjg{4i623#lD~l{eDFus z{+p0N@A)-V;vXI%aD)^}I#DsuMr?m5%0x)>fV==2L>xesY26Fdg{KU&bpiarB$6}^ zri>{8$5%lF%Q#@YwPkQ=H8`-`P`CE1;i%3y^~cXIUO+%Z%DS5F#J^BUJkxVrD#um- zYiRock|On*NB6od+iR&4ZqkX;{TaGMX^%^=IW3rS)$7KXlph4w#tC@=QhKtow$AG_ zTKeI|mhZCoCL9ytg`?Q~Q}{9vqxX~_YKDs9j%zwJjvsQoGv;|QYbOKY>7q*U26>~q zHrFq}xX{Le!}`+|2x7>bJ1!nh4?W9m^gZx?0Ta=h18O+5#UT%`z!iL!{ng3^c1Qh@ zk+8j}Agjy=?*~4!6nCzoE#2;TVqchF?RU5Ih^mG#zaM2t&tGaM)Zcr%EJTiss_$_Z zWjV@%{0|`zX;x9_^GM5E{3utFw@f^~xAKDeX~z78SV7>b?aPE~=HYCsAyy58S{u&A zg`3&`0CjB9-qh;s=cAAgwZLXl9DZ)yOF?U(V=x-NJ^~rY1NyqlRBB?0on`4*atZjV zrAP5iZt_O$GG=x%nHKTzHXdXEd$}7Z{r@@xvCVR{{@2mOsq6T31+MBZP#Uhct;O@NNM{@su*Vn1<>RnA! zni+9^!SpKbNE__B!kUjQmAg~iuI)$yL;H!9uRi^Hdg>E6&fFlEK8_nv#NjT-9XzIC z90$S8;|}*nZ_G>YFOgnZ3wjG(hFv{KfrC%YZb=g-UBIt&Ts)}FL?9!NNC)mGK<2B1 z#!@w{|Du+shsxvp3yCOjj2a~owfZh7 zn&K&O3#ZcJ0*|u;13y&N*=NpJ*+7JKA5RB7yPAH5yRR=VXsJUdKh@%(rM8!_*JtU3 zR3#jM>}`Yh>AQXkN2p%bn`F+g7NY$|63OStujyNFnz8pjcod3`rx<-w;j!+w8OHBvLUPpWMNYu9-l7o3t}uS<1$Y!dZS)Md|x z#q@)T)u}YA2!p9ws|sm{DV+X>-pK*h=2%+8-JdFcH~;L4%MFML^iABWw`?8*Pi+hC ztc0}iy-WWtH-%K&pQXll{~-Y!GC7x@?i6+w)) zu!)w9-YRWf$w022^xt7#pz4h&9L@nIZRD!77171(F>)T(f>lX{d^_#S$>)L_RG8s8 zysB!(js0$*gDQp}K39lgGtge-Q&O-SOVsw3Fq;^xD=0Fkpo#11hdpG%=S>{`920yw z!Q!j_ki{g@H00H2W^#lsV3q@=3<^aENSk$dJnBbQW{vq5sPSRr!m2zMDsRuQcFMSIE6_{wM!!xQO7LF5D=59^#u!TCgDYls3(U87GyqgiYFSUZ&Bo{7OIxYSW>V(U|J3$N zlH=#464R{XR#npsDG6f+%XaWiR^!8G6|4#uIBrG6QJei-!0QyX$_%N2sZst>9oZ60 zP?btZa%r~IT&e+Ts@-uKoCgY?RK+0?@Fz6f;TL#CNo<2~QSPMJ&d0wB9rkzAG9ONu z1Z;?j?gwe7UyF!!?YkH8`_9;hV^YH}%Gv5xmQJU-TaDD(lt%mKScqipWpzj~eTOCX z?=$z&`03{tJh_ z=`OJ_^0BOiG?Kn2$i%qkd{RZJZMBrQLW4yw4dgV8bLnS zJSC`J6l*w*f`hiw`v&GxVHbn&>r!!}C2$7Myd9n|$tdmVj+1;saP5Lj^Cg9gVJ?zK z*By?JiM^0wd*A^ zuo{59SnV|R`7}i=VCwOm#6oABb}5P(Zo?}qAz_K9Z3qMSrSikLm+ij@HWIofYnK0E zOxA`+gUxLW?e00uiv|jT$#kxnNj7CYHdit`pck)h3Mv1LZH=Cl)MljGzaxvCmY7yobQWO{n&l_Era>QcA~DzND?f*x;<`ipcB3SRe~ zND!LcWDxj}0tUM|=TG$KH*u}$rHb4`o2iPKg^&ej67MJVFF3Z-{9@V<_j^)tzPO{5 z7o}I^F#TXx2scNAiEmI9&cwk8;f6w7*q|yW_F%5_u+S7$P<@TN=Dz~p>x8wG6g6y9 z#U<_}Pf@$WcbJ;@U>N>Bz?XV(uh7A1of2|T^`!p3zZ%(3x|v5*_Pm9H`WfM4RgxCA zM5WY%nt*ouc%pFbz!(HX8| zDt1+S<#b#BpYLT)6jIuBZ%V*vs*oJmnxeG(vMPg}_*3YBj;s@2{IWykM6Vxt*~($$ z&6K7}NqikpJfFim0TzKfXk|XckvpWR`^$E8IpXgnp&e)@2s(HG8J7*I)%$prgIqnZ ztci>$iw!^P+A6l8@ecsG{TO=36F&_Jp*RL>#=QqKo_|P)N=b;Fk=|{G{A^(cwt1?D zzq(}f^`1nt<3Jd-y_7SX>bJpq__|t^{VR4D-q@BswZXdki?Xrt;keY5K3n41C5PnK zcP_NsoJq`aO3!nzH(7~J#2SVA&h%SA1=(Gz(i@gYFY6fo2l=r5NWhHeezbpW&+vl3 z+Eo1cf`%Zw9aM7{FM|>860(rnTyVE5Qbe>|&`p(|9QUN*;&bM-qV#oVW!j#BtBx{? zdSAJ9br`hpUh49A70IqXBpYTNJ0oCHzID-@rxRTNaT+OAt>Ms#ETG^MaEFuGtY;%(Mr*6WX_Z}tu z$zxiVH?-L9?BiXt8LI#fYayRFB>A%~Xf;N!$1mwk!PUI%yzbqlch;LiC>&hP#2AKM z{}X|53&QVbAt_qtC}kA*yel!;z-H!S@bBK$j^tc=35vy`9EC8NuMV5AbX*=49ESum zZ!8UU&rj$Pf1+DZwiMNr&hGRXg#pA%Enwb78+QGBuRro}iLd#M?9Kyr%D-QJn6`KhOzBv&6Ms`g%jx+d;GLT!S# zd^e!J6G`mue-?C3@?{~ITE7;@7<+n73p3cd!byzLoJEx>yQjm)=u)kF8IUCuGf??p zO4ateujGJ(P7Q^nH>u~Rnw3kYWL$#*ZfC(jf(F^fmxI;^@Kec1uy_1+j(KX~?o3K; zt`Ku$$Q;@zb$hni5dpB%{1n=~Px`!BBxWTl63HJIui@U6`RFK_fxmpQ=j1V5_~^Dv z)Y!9!zFg>SF}OQARJz|?z{qR^E&t0w>`f15UVDBkzP)_B+k$EqOV9p`;bT*6Bi|AzwqsgwHqX&}e zB?lo4?&N47bp~V)3ofWyv!P7m-OK^YOCac>J)*V-n2BT^1Dl|yD`A={4GlsF}fg84dfUZWM}iu20m$kkmM*B;VTmq0AY zRaa2`%1vABKL$X}JkE2*8>_`ku|4#X_dM<{?%i9gSojAkplR#V(tmy>KS+S8H$;h_NQ^#zBpph~~!} z#f<)Zg8WjORjo{qQ44SP(-(1|Cv)%oHGLLdU!N_=RZ&r~n~blcM$rNU1ZBB?gKrOn z#$G!P5~7dz%=60pP~{6nFUN1k>L~CmhyqVrNWEMlsu`nt4P=D(HNrhp;&{AI_8XZb zSH-SlJi(8(s#{3V3Z<%pMB^apf;%)EOPFKK(q|!kEf)#%hX;MgRXcCT=R!Ja%a(C8 z`~_;K6kYj4w?7Fap&3`(DantcM-O+=g_1KVXaBbHp|2gAdv*ugJ?sH(EXl$8#$N_4GR=#i>kcA%V@ z^OirVfbER`u?ziC{aW4IX~~|upy7=v+jrA2!AtYg0h?;vZN1R*f85+BEo)O`jCb^n z@H@3*Jq-^EKY#mf8OWZXp5!^0iX|%%3i!hm4tsb&N7fPUB^>AFO$qlQ?}bO}J!~4( z08yBvwa4>x%HW0&NUGZrGrb~|G$E;d_FC-I}ekHtq(-^|jyzO&DnTuSx&wZ2Lk{5KYm7S zXl|%f-1C4BPF9e^iwD7~M#FAEr~4@n3p`prvIbHO51a~X#fwi#=f2=PCyI5D?!clTJ9|{H>(UhGdPZY*r# zKbO+a21Y5ZvPVVUjJkPi*h6=c*|{CiiC)dW-2_oG{R%cs zNXgkPZr08hG7FAb`aT(Z7AO{m}b4Wi!@>zs!@BfKYJv*H%Q`}U`+rNr0gbSY^NCVGpVPyexc`_JKz31Dy{I+ z86&(6EP!?wp$Vs zt96Y@_tgRq@-Eml) z-%(HMv#MX!cdk;+UW+ewG02PN7dH5yZ$o0`kerj!rt;w`w$tzLiGg7(qS@ZI4Rq;6Gpn$AaNfnwWN#prz zd0912M)_I{JNx_poHb6c6!4$q+xs3ePm!~ZHm$5xUe(XQ&sK9no^*mVh(!hGg?s_bTmjQ^Hqzbno+e~oQ~4e7 zR-5Xh^pAl&zad`rUVbeTOkbw%We3J)>!}?%)U%uPM!?VnPERv$$6;!4&i}x@k$MVV zp!ab~Pm_OrU}jh z{2#DHll5kDQeldbDVv4Vm2i#G^Qe(_yCQJ?4-vz`uyU!uFnFX-FF>%@DChtQgPYUW zdJYJ4J>k78=C0~5j6)*b?uGXU`E(~ZJr6z&Q8oPFvuO+6wv*exZ3#c*$Ip}p&AI26 z?{VhLD7krT95*!Q2*yIV+`m^Q8T_)_@>1-(%FI5arrk8Er@=`R z=Q0fE9=bm$Gc$UvV!UN`D{@Hu(vA~<;OW*)AbVAd%K&{1$E4>@RLYF$GYI8Big#g$-Cj<(zqY~JV%a^H=LszoQFYvLA2gS^(jv@3i z$1YDLXV+%PCSyGQO^hx)y?9Iy@O}C)$DAD~Wi+*CTj6uosCet2oC&0i*hwi&pzE{A zE8RN#TeZ>tgs<1-1B1(FR{o9%0RND`4(zB0qB_MP8>8q zoe|6Av?nd(%C*oO#BLSd>y;vKdjev z$xh#<+oN{Loh;N)Mqg-|=e=!y@jYSV$lacr;$lDMWoMlK^jzevK||7$qSZKnyT8># z-?x%u(m1weol4uq&jEy=84-|^2FPDL5b44Oq)DmIGp|zm>gGTtkW8Kk$L#ob;cTv3 zBSdcTTlrgu1rdc^`oif+)~D*4=~?&;a7a53h&Fgw$>>EMG-Z+%c%;>oL5B8B+ktUp zTvEHHFSm<#&(Nin7vs(ku-cqcw;N|Wr-OE2i+3Yx!cVbcPQ*Pw4np6*aeogT;~tnt zrr(sfu&mwoxk~U7=vnl8H@ zKLvzw{;^{BZyrFH1iY2?wRsWZR8ST>({Eoj_;#w@n23 zKIg{_4olB{$GUznu}kRFGi@{7IeWv80;g$tidC*IO{2U~BhJ>IrZq1^d8%u43PEor zrix$~wZE}PHt-@^#PI`E=~N;^ux#^#gr4I5|1-eN1z7e1D~rKG7MY< z*YC`eMH2Haz+HvWI#?ssg=~2>K48UEtBe2Eh~2g5+1Y-=6n%c%mkm%{A^ zU~$YLYi8A%X6tXTAb&Z#)QxYEI(5~U7t{P!u%`#dZ~U9AZw4+ndC7ecl%EW2X0&N( zrj;y@CO^3+z^omOLb8E;Ufh1fY>xD*MBWX9B_eaMn;~bUAaMPFBhz=Pgm0}*U}Am$ zC+}prn(6}oT~vEaSIU^I#x_^E%36J{a@5CoJAcGhJwV$ouVE)oPp&$FI1$_==6Gydpg8a9-ODJ*N-xC0 z)(3UbIIo^pHZq2v(RS^OSoh&Z|En8??=JVtTd&Co!atQAaT9%d8zR6*rqa&E{;^ii z)MZ8{uLA+|9Qg_|duX&+WmPhxG}!8JJ7a2xBe!U`JRaL-cWnO(>2}=WteffsR?`kbBoGPw;}E2otwpOonbedy%UxpG;5Z--Nzu~xPW_qA=zdcXf^mH}k=Esg_{{t?{xFJmN258cu}8gEV-Enti7^4ntTp2F$>%g++?%OYKfANn-KNw%R1zr`^M)z^ z@R*O|rfL}~>j`af(yhRLs5S1YyE(l)L{m@E5v*FpOjp14_WdMaUPcjcJWF=#j|?ILK#F%euJEW7Xcp z{?P8hr`F>;Bx1bsM7CfURa5LJSot`5Fcj(HUE7J^ef2O}ZFGAr3A4+4CQU$_Hsn&? z{XA&Z+~3CfCPI-E$=AI!B@JVy?vmh4ja)T4eexqe` zY9YwPqz{tF_FQz2Py4c9)X8H2Lzy8WpY0oHE@{bX<~#Tz#268}_zYZLb1{a=TD)!@ z+b!?-`oBGzF zeD2f*jMe@qESR!6Jx?@5zctV+i0S5&1s3am7MR!M`ZWvB6SFc)`s6t&_vZhayF&b~ z_>vPsE`~-~!C2i|-~Z|3_raEJ7AdV_&^n%2T|&EFZ!(Y_LiYW2A)o%dtjSGcE7F9G z?>9wZ*qY7tr7*6q8k0P#Fix;I2&pr5HckX7&1j5%lm4BQ9(W&qso5X$$e1d#SbI8m z%}yf`u%qWNIJX#bQyI|NWpUK-ivpH-^}XZ$6RJ!32mhCz?`z>+`G2y9~Mc9d%o_QA#t`HEDnx_i5P;>%CQNb`*|1BZ@L`oOjZI18EpF z)h>rqr0bJltF5SF@ly-4zNr6ZZ=2j|AKDs9>4P%k#-n%hM)5jzQ(ufBdBUO>pF_!R zyTl)DY7kOfztM;;LcR0sFt#R%H3}O5md_qni5+I5;A!?l3kmI^bsyV(PZ0^ME{UT? z*z)3M+q#Cv6p6jHSn_ErVjrO04E9}gcN3Y?-5R^Jkh&BAYb1X?{UzQYX(b+9LvS=~X!)cEIxnC-Zx2#!14>s-0B9QF*xTpA*+=Z(@=MOYi$T zE2#+napFlcDpOjQ!637~R6IlEoope4ygu68YESzLMbzo#UO5}~F}0-2Ui@lE{LnWh z%5$u!skrqYHQ41W-RHOA4-4OXO$2dp?zVmW3~AGW>xS2n!yvs|;8)7JAAS)Jn|Vnk zfy>7lDu7+z1I5Mys2hS5665}MDf^yHyG3Mm^S&nrFsq?S( zBch4L57=n*l6}blMI25vO=A8%^5AW1Qquo9mE>KJ7pYQTN{{}Kv$kz7GEEv}@~Q!9QGD&YDUl@C$ZUKj`N=hR;{ zuCCO<_FAw?tFT(*uEq$AtDr#G9d+mOLsjFR0U46M^U>zkQ^)a>hwg&WoaRB4_n@zL z_+Bimb=ZH@!T*D=8QE*iZ0$;47p?cX-;I7k)s$8yHDX=XmN;u1Pn zWiS}Jn?J6P6OgEGY`Ytv%sUnYj63|}D7AHDsRrw#&X0{(Sq3m#s*hC|X~s&^65yFB z&5?%aTS?g~crn9k-%865 z3UyKzJFF|iw>KoA=tRd)tXkNXcpudC%tt`#saJxsA0$OAv_5+yP<(y!Xa5QNLkEM= zaDr2Gz@6(8!T6KD>YAz9REY`SNoVg7-u>i!t`sa)k8i1c zLC0A$oBL)9JB%9Zoit%%Io8@=<}3qD0f>gbr_qja^3z@~J6h8TN+Xidu;*UH@~!eX z4PDm}=v)%{w4oFH`S(>n!oBKjyjIjk)CDzk%L6YXe32m^$3-q_E>BEj_{1wi&h|yJ z_{fg{K4t*cb}WkQ@u)E-Rx!U7J-nMP{SnGoj_4|Hf04rEl1Gm zSG;hhsud3h^`3-^Ap=GiPwnjV;qqOc{Kpk@!RQ5@VS)vF!p!<>MXsMbQJjLeRNubE zOisHnbDM#$EAg3`Li{clqX_Zf;h90z%#z5JW}PPN=tmP)U+aUg7H5S#vAr|ie72gF z!hkzx)CiF0U2tibzSjTOJV0_8Mz|&2SW4v zHbfW6Ph=YHO23>lm8HJZ4Y2BF7)E}|qAj<-NAO$#}- zsphL*!!-mqQuYJP_sREYhyCPyfZaNld#r_Y$BIsML${*ZF7Ia<?__YOhvU!6 zu;0x9pvNHgZg+v!N7p2F`pV$O^QTLg?(CT%)`2{6lh&f)B_ftwED;&OeVabC)*rwp zok^bI7F7WI@i8X*fZyqQu*y$#{NE`v$rLm2B55!(7b$4q}bYgkyzk?%7oAPmp zt*z_`;QHm;!q}+W$m%{0V_@_b>ixDP!9vAEwIqz|?7IjQH4+VFl1*UrZ2D70E-GliVXd5%hm@8gAz6I-OtPR@_()s#ebRwTtiXPS11)fq{wP@BbW*Ajo}3^v*E? znr@w#G3WPILqQf?@anzb1v{Lqtb>8Jwzh!N$+BO-PUG0ia}ys`M8_Poqv)lAs5Hs% z_PPil4gvu3m6zb);AGcTo_a2Ne!K>ywHJZ{-tmzC22u#@eI4-BI>1BI ziWUuVzV=7|%^FDv{%^Pc0%3prwtxDc`;YuV22-{A-;76j7c`gIYqzdYP3rQ;{sP!< z$(_6ySdQNj%rWaDDBhlOv7xF1wrDLy zAI?9V^z7j6r3C(bDdx8)dG5_YZaZllLtIt9T^mArokqgv9{JJFdoi2deG`5*00CYFowFOj zTE(aJ8vybz4@l|_Y3gv^J0^^u5- zk(l^W64DcS9BnuP`ziwlatFH}A+HxT@2%JOtX-F1%37Mfd62d%pyu|q`FP!@bTKv8 zS6a)>4OshdoNsNlWYB!KvX4jl2_hyCg$Z1u7aA)_RigJTlZZ`2iv#xGKmR}e*usqx zfc*DA{U2Zcxg3`ngsFJ8RLL|F-NdPQi?6P$eK3MBvgLf(kNJMpT$a;-ox`G8zXKHB-gS1mz_1 z%1q_ggk|rAt@OQCV(+2TYCi4NbsmxC=c9BO{g>$kBdw|Xl7tT{CBHyFM-Lrgd`)Fah|m@+{>Vs|F&2EFrrTZl zym#!sM`x%MQ_ybzuM@FCk55c=9f2-)>I@gRyH?DG>g|S9ceuDHM)5k9i-9cNvADS> z7RACXbR0wdo(%j2^bSV3ZW+Bx>+4SUdS8fck}k` zl{t1PI{Bn*gd&j1Vpop{6Z%sqI%;|>GDY3*>|h|LyDNI)Fvp^iSIlb&ospGM(JejfPvE9X8V;qSTBdLt0mP_{mqc^c@zW z<1Gd2ev>}^&aS&QuPCvlOMaqGhZ*$sk#JFMJwpX!S6;_Y)_+0s&wj}>xtg=%nBLzv`k&g@RIPBi3L^k3e)c6EBSuWZf z5G~+cob0Oh>-$)$MHEZ-HgQYL-nYhbUn~yz22I}zS_?oCl509jPQhd+S}w^iMCPQa zPAmeumk^xF25CCc^c_?110j@O=nfp+&c-x{x*75>%m&XrYH?ROFY{N6e@SmI9r$<7 zXuUhA3NaMT#BMFZA7sZ%&wC6~)-P^?BaC{vqi%w(il-ffpNTE?ao! z!3zLx1@9ftuL$%t+lAkI@FMe{A;I5$DyaEsll#*qfXMk_kJ$b(uY0FQJ}rlq zt8FJR9ygj^06evSztCW)>sQu158w2BU&o1)vJy~bj;wE+8*_nmb1d(tm23b%IL)X@ z(wHCxX7Nw5z>9{E$p$RZtZ~mnU_Vxd&6ns0X6_Mfo~(Ya7MJ*H(|FVrSmUiQ_dG_W z==9xL8I;mdZJbKhDQD;f#{n+>paZdhqZ_A*coZ6jzg50MXkgn1TN)Lx4^h@hU)wk` zK+$-ZG+(240A119P0REK$z6-!W~pivGPH;iIOWBx?pN%ml>V_VwwCZ0SuvS<6C9r0 zzSJ)n0o1vxpmqr%5mp$YFDd{60`{1dW??`?+(lKQXi=Y2c!RI0eS_+H@5i{e{O{_D z78&J{x5;0siU>vPsiWuZ&JE64`N(89%UGT@GB!tikoD}Ab#Sc+j$cQ(`c{+>!5HLe z^b$+YHrtWe{I-X9wC6Cbd-TfEL&-J~Z|7DBkU!6PpbKgCfDiHX5(eJ2st$)jI%?ZN0F(Oc|L7P4y0!NuiNzik;8ki*n` zifYEwhAH=PJc@6*d50~NLRY>&qlphHY>K5&07u)puZkNW_HFxW(89Ak8S=rJgW8msP@Jcku-vkcMFU93G=&40?6kKyL`PXsU9L2h^<;9wPdNa8E7$SV z3w9mVwTQk4#!o|DxcMo6LB)iQ{UcP^`E$$&UW!D)_ipC&+LNfi1Fw7NKu@9A-PWz7 zNz!eWiVz#cUPk2p?sK9*%w?r&Aj#7634QJj7%%#=rr&57=d%d$Lh0&`q6#3tc?kK@ zFXT!HeI@bhjDczOco=Y0>8bW&)I2KevRLkLUrXY#oR;QHxO~zbYoV=Kv1XK-02u5c zjRM%K`W)y$<)1n|@Ld<`iVbzUelLiR?kqx7`+?ibF=I&>$#7Vfm5a673c0%|3g&x8 z;b#I{S^*%t(VUreX_emcTwcdvw6J;^MM%kn@waJ<^{CwMH1Qo5fs4T+mV-HEZ5Ja+ zUf__R={y8?+5in+jQMdsG{c#ns=)b31Y!Xo59U7=KNzW_Gej@RR;nrL6+>G% z8X9gr`oo|{Z#3Z{H_wQwt*Kvlbno4W{CaYw<$KhDB>tk^j(4d|6`&Ywn(3(8ZULcU z&yVIapZKWyIAL$1ZD=u<)iSRl{3YbFXQ_ExP`-K)nGS|l_nyp@T`pmjXb_|bHsZ_f z={1h#7sD>-5H<*^O87|_3~u87<(_3k^!gg8-ic8elQ%`z4U_^jceN7K(H=y?@VKrr zPYNZblr!77t*r!Aw#c%c3I;JxQK(#`Q_muV=LG}c{MGVUbogFxc&FO-2S7OQP0WYT zdKTYjE+0@jn>^VkpH62sBY_Rh2phczr-FsjARXL_e%{RCcnkPESEpmc+sgq#pEdK4 zxy+a-orAB={0Ta_rVGD-lS|cSo*efwHc*V0REhg>!DNr1pAggVH~-8*Wu{rs#QZQl zFAkQ{i0Jb&w`{2^U@}F$5<+5{1Mw;F<9poO<6z>6V#W4(kP)Y9S=|Mxc*npmEJ2xC zEIiS{2ne8>1%7)uzHV=#oeWfKhi}1wuG4r+BclZ#>*Cj=mB z#3-bl6YB|0CC%=Ub8xK^75*~a*0K`ud1(@e+fM?@BnX|7P)M`QNmdf1+Q6we=MD?H-W1R`tl6rS2&b=udATtD}CmsI?szJ74A`7@eRfu znF0Id$>GW=pTBVlR_I6%X)&UBJ%~WjVbO)SGB`?tV0muoI1!h6Nqzp0(Z>|}wI61` zJQndk)r;~`fSa9+kj|dKxH6RJM`l`p3jAAW1@2~8ZIBqsgb91HcCy6UHO)mqk@>)b z7({@gUkhr;!65RCAuuwUr0wW3DZ&*pDj7V6KQ9BIAc3SazQ&-P1Ii)#?TS&Y+adQ66npcZN;tMOH+hOZf{tXR7Qc~;s zm&9(vjzPhDGw=(;`@U|R&!`=tn&()n3G!DBGU=keR#pichS0PoM?QZWm@P8s1lH{i z6dz|ReLcvodigc7`J4ANAMxd#oj-OZQSbUaUtje zgg@5Hhz{&MygQ}Bn@#Ai?sCnvp)deGOd2oSuF2bf$9Dv2 zwWsPm3Ks-o%yv_O(dJe`nhj03nvp!Dt{Oq~oViXAJUIwoa~;pEF4TFsDcLDebv~)^ zOUh5puhwJzmt)&=3+)KlIBdF>8~RX^q^Hi}IKhX4Ik_dW;W|ez=#-3yrubc(e(^9q zt{x^1K$rs`W=D6lXKhhh;OkHPYQF6C3@kJSgD|(%iXKg+=wO#r{*f^%g-#LJI4w zAP75|oPNDdseKHI81PhW+g3gKHg=$ayJ{N)$)CIVp@(e|foEh$4?SIhsFu!>aw&$> zGjE&qy^b6(roemvpZnV)+&Gj;@RR@(crVorPEK`OYCs!2IvPS5i#nnz)L;R)PX?2L zlnc!C+AQrU+tpbU@&y85F4(S6nPA7Oen@zeGSc<&r%Zd({;`sa`FGtjHmj?TCeWK* zKjCl{q zpwS{SzvbuKZONPfvBj(0(`8}C<_yY*Qr6h~=jpIt(}tI{V4kt|D-k=RJ>i2=^^)YU zJ5)gl#Ans7eTE%uh4OottK{Q2z6Su)=ak@!%fH-eg~)eS$Xz25s6)dgTvaX#k#B_g z3ZnBYeT0HbW}y z%CL`Yp`EVUbwp+a{sm(ZVKU)d5*5Ou?&P@BYo19%9cZl(sPdVgQp{oMTK(!q zcH<`6OTw-N9qsh$-A2}y9X@rqT z${9TF_)bW>!I_Sb=ffalDzl&;+K+miz2dEzEP?j>m)|j*(G2eT8&|(KHKuV625%hT zc3fl!G^PrI=nT~{%Zdq|+Kzcnig{NsrxY)@!hY75^1B#6Ev7wAh;6%c;QmnJ)8EBt z&K3-6&iM7Tmq@58fuK`tfGT&!24hZv%wrn5ldr1Kp#v$}+CjYpzwK(q9tMx3c{w!c zTBH>Phhob#cEG8V6Ny$N3S_-6-SJP7-e{~7V=LqiykG%Tb)sc}g~IS`!BsZo( zL95r|Q^BNJ1!bOy?Sf^N$U#)|mv|&vfhYKG$ex2btS$bHg5=t5(zHP^!c^1nlA4pk z_xiI|4=Edc>=P+t8R?$s1l|gJDYX^j$cf>>)Bz(U(YI>b1jZRu0Pl`&N|FVBUszO3~a{eUe5a|PiD`8^XJ{$3fhiS*VWJfZPFm)BlP|;O0yiU!o z)VRKl`TARI*wY3^ecf#gA5@5g+-t>O$mR~Axv(D@{=l_ zRP*qv?JdjrJ6z=C-YH1)&!p;t zfQp4Dd%-4C7LWE-4~nplVnOtHUnUK|_j##M&}8#E3TuWlLss$ES6{9l!c9gIo}>;} zA`oE3G0xIWW_-KpZSx=>ooE0dUP|5-!O&Y-`L7ZALCvml_RTA$`|+26l-j$$!{1+| zj#tMoyMzrk&bH19N%U+3@!NG3{D&17bl4n#(q=+0%tmX?0MC8GEAv>VIYkxY!y-b& zN_~@3-$4NvM4$C`D!-HChaTIl#yisYgXz_a&~0bu{?LKW01R>6kx|Cy@hQS?(Qy15 zQ_fu|L;mhXx|?`VQ>2`X+R=Q`4`M?YOh9VqVm~+jp$20oXEb_AL68UpoJVa=isR zA`nMTz+GHteH7_4>3(76?d)y+8cdnyCl8;ah!iymjF9)0=niiBG}J*1a2vC8A^|{J z$*po%*4r~&qe`Tz)6KezL(Yc|6BOi(y7k*jQU!pB zePf1cCrzYQn@}P>P$t7Bo0t%hjqiq8rdNxtw&mZy4uh*Ko7y6I%An`k%=CiiS z-ph=klDwAw;1)y;x7!d*@0kkxn<7d(^!TilD#hILbOvCG2!{*Fa2QKT=?_&|pYVw1 zlcEj`U`!&9dR=tz3z^ZNso*TWOnjA5y=1!tyR!&&kAAv}orLzGQ@xSSDJ=I<8eEPP z7RAt%6LHXH9L;szrN>w&!==kqSma+6cndzG(#@Nk!Ln00=h%CceZ|3#;C@Wwa3OIV z&3YI3_G(;yk2;9Pe7Oa=y|1OiIQt&KKjo^pS>o_T_^pqnK%P=b-S_m{zG@^AlJ`jZ zpiuuAE-dRZOY7EYQz;4VaDaQx4yZ!XR!w!c(nK&O0PWjfNUg*tFz_`xL*!S z69xO)7Vi!$jCMF^z{g<|5;&0dIh%>#;5d=lpq7k$jP~!d($qi)f=J_4DE(Mgs>hz| z#o=#f{F_2_Ti9PLM;JWzO0G1X5;Uw2)4gGnD83@16*~uxGM%Z^_}yqI@aNwg<^B!5 zppqo;h2mRQj`khDe0KbzV;M4bncmm2x#-3i8scsMKP6QxoOfb9D&oJ@>Y--6VRDx& zP{Oo2B}RC%_qBEhyT?bKkzbR)Z9ewvtcj%V{g}mJDR)BQepKX(am>i%9wt)NixgIZmVNuH=~rP1ra?r!o1SkMbPIb zRD>&}oz9gUw__SVON!%`zdQ$GDv&sa495`gwvdk>BRvun>F`f;1@ zMiYJ{Sd~(<%K>e+OyD>*YZOc`z6i7e_z-lomk!+0g9RtfROG|M$8X(SS?JfUG5NI2 z?h~~GqK$?L8z<)`thuUR22uJf{oC1t4*LAs%ej?Y(ze^OT~kl_D*v!uP_c1J2?Z|` z2dl8#?ShpuT)F<12og}MR@88C_>evKlIzgch#8!92}h7&*>RHTA!o?Cke-#+}= zQm&q_>y%(pSnEzuvyeHPL2!lQ&7SX?uoJlN^@&0vAC0;OCJ0rcwQTATq9{|v-W3&r z-aI8)Z7_kza1Y{#TcL7yqS`H~F(CfpP#G2br81shMTniJK8XFqf)GmS7^Z_6CF%q$rjyMVkP*=mzB|Eo>;TKWT3+rRv)-p?q#P+mNke(YmDj8lcMxY)F zV=DdbNgN4L5K_cETn0JV#KoY~Cvbw5-;D+bY%JAi`p7qY9WU%06NZg1DauVO34$TT zu$+kNF(&fglgXVfp${2e)%_cJkrggDDDzaXb3_@g;436SEFuF%=0MVa%y<|$olfBA zj)q)ig8T;M1DWYyD8UcrwVRzt40pFDtXIiLp^~FQ}*q_q6&&uyW0C>#&98j z3&MDFyiDdW>!fmQ$qtN#j~4i~D)9zoD+$I23u2$CMc(>xi-J}g>jedpm~MIX)r%+7m|b=66GEYCwZHo%bW_=a#052>H5^?^6uam7zO4K zg%p3~$G1)O{Js0eSr1)sps~nm52I_C_L z>@A$x@>uS+YIFm-Xg9gK)VwUF4_AioJg6{4VcFD{-BNFc8rH^ArRPc{F5!#AAs}sC z!R%)i^Ryz|o28GM+AZ0t`CGM#BXeyW`zI~z)`1Z97Pu3dFxOL_v^rCL2ajK_-aV4d ztL(6Qg$hG~YMvxQ*0F=_=k6JNhx1CXT0W_*u%bN)L(OU>BsdU@*mf4FX@5~RO zK`?+)la^kQ2>ZyL$UUTBcpNS*r}gbmi(ve&=BYrnV1=9#5Cb@mTlsO@8)6ok=i8f> z+_omlSkg~-i2CuWFh%BY4)e=1Uzan-$Q~3XR^i626=BPR)HO0yRk9y63)jgd(g!yI zZmoOGV70s(8AA=G0V~B1%oifMZNdhWJ@8~GEw1Vh2jUx)=ps9@{~{0{1h_EvHqI@? zQxrz+H_;?~)I@bv@2p~fCto6BPv=ZV$*Sb;T&6c&2F@U_ZQA>mqv^o>T6g>5v=CF_ zA{a>r$=}Q{Uq>wlzLIYS+T>;nuJ>jtx# zAi0~pool9_K#@Zw{lQF&oA?FIg^yb)>qiK*m;CY>oI6aft#3(br`H_Yd^kJ{7*<*r z(;zNQnPPuChhm_+RE*?uh(1d$7}3% zIwnrX6NJ0{Pgj%$qi@w|HR8>yNjF0x2rM6w1gC-c>s8N%U3OC;-&Xm3&Moiw94$`H zZ6$vpg1v*cqLNEFy>AzP7}n!pgWdSv2_hWab8h^85r+S`0;X5y37?fc(rw-B4+EF+ zm$nW>;ZlcytfbaJQI^k9@P0p*SS93U%IDk~DY^STu9TOD#sH3b%}fv(eRqWpNs4zO zdG1qwyQ_bWt7X0b8sS@eQQi%dkJNETKF$fwb{Q&A9P-j(kig^&h}GkoLiQB#oZz_^ zL!~}M@UVRsQ#W3ueEAcip&a(O8cuN4g{gk3t-J`-CHB)XOZN>bkl*^1eJNDe@B6D@ zAO0sFHj>Bk7zIuL$leKk)hg0cAG&LR?V0D}U0pj0tQ8|t#tw9$G^Dz`g23LGv`=T( ziv^=i_N0E;{p{xb*j@8fX8_}E!Ng75P)rC!c81n{JI%d1-2np?c1T=N@}gQy77lYs zC1ZUa1eT~kRt%p}lssTx+C%Eomt1d_nUN3^A$u%1ytnH{%EMy3`bES+$MXX`a-a5% z|H@5n6(UCxQV}nqA19=bO68#HcR8Zq)2{@k4k#9JU#4NKZWZp*&5Hz$iy_MP5J$Yf z!f`F8juZ9M{`Qm;(ONa-qxa2tjdayaxyN-Z^|ZPf_tULl(}}+$r32k&$8q%OB0gtJ zwugV=M#pvZ>G%FelG@(#OVUJYiz4FOCn@#A&sC+-ZjvL9AXy9$lUOzFE`rT_fGJ&J z1~6eP-8GVF{=b7#+XyUBo)jR;Iw~tl#H48B!b!=Iy@L;L;Gb@-t3kXlTyyAv+q4?KDH;T!$*)TZ;3v_K zmb^BWVe)Ko&4e<>aXvL5zJ#6Q(4~ZfZ%_-&(*&%b_S~>%G;}b%B1__h_M1lu#?ty& zO-*d+whO^ho@~ALmz=Dspu8cir6esG0+b|(HX5$*`!o3;@lH>~DZ4Sj%yiK9-zp$1 zstq?69p(eZHghFEEvEfsUSA%93N}h*T$W4~S8gl)-q_;4adlCeJU6Y)Kd#XZjyc_of6HR^Cu5sZlWw`V~q^2sZKb<<685t&n*6-J^H zYBGO65Rb`Wvke6vT|y};H9J6`Y`S# z{JPJ^S-bX8QNaFlc4mAoOq<`->1p^k!HwDuu} z?}NpT!bNy~0d>PHCAR?o}^K=d;Nt*Xk*P5@(%c8Ej7Z1%tvU#ffBEHeYB!jF2t?cvYSbq@r>{V9eq;4ZnM=8SYDa8Osu5^8 zU2<*NbzG53kqq>cfy^-w+V(Z!z5Ho~CSR+5 zgv%>HytW>EzNb&T-X~JLAM4uNmxpY;*4gwMY{hjOe9d&*jmaI<@*2GTcuf{M+=1A* zYlEyUP4btQhw{ghKZhzfa~iy6K>H4DZ9i{2s|-h9zPbNc6>z_PT$Aj6yPjbWe`(?? z$|D?E4IGoduR83-UI!UnWL01xf1NB5RNF%2%WOZv2T-A$9ft|2S92%lU+L@mcVJ2% z?jhtDyA!FF-p|N$j2w?OD#DV)FErBx+|Y5uc((yDEehGkq?4b?_OFOsf&g+xD&#?i z3(esP9#KUm65$*#eg3ZePUba^MS?F6^5XM}E^m|G;rVPB^;aZ0C&>Cea7f&K4Xmf! zRrAaBo-ezgu0_>BL>>EJ5KAb=!Z9mF-dzL`RkX?qzg2Ok2QPkh-GTY=oRvU98!s&f zkJrpITO1rBK&g132-S{gsu1l7wKE;!!Y|V`M?}~-M3>PWAyjE=qO%A8-Gh{Xzi0#k zfv<45FO(%dOMIrV(>NdalO_r>;f_wyB@U)CE3ue=Asi;x-Z|L9695P0Rx~Y!D9AE( zie`AIwmG&f-G2{4*|48V*7nQ{2rlQi`1mp`?Cm!$<3)kW`R{lS9_$JmE$YCV-BU0U z(%2b$x05*>BQy!vtt6*>BU_3qf%?M-)G7It3oMchfD@dQ%N6Hv8Tz$dJZNamxq1C| z!`%j9^$>+OC$718#gPTxa)^D24Mn3D7FTxUhu!?196hwt z_&Qu@Vz+?vOf$R+D%lS4iKpO}K7UKO#fxZ`);@Y|;5wnzfoiuSYN4G@rn>@flC5U* zns9K3cUw=2@pra#-ud;d-XxGs}6!gZE1~ zH(~Dgiykgw&7>(Whsk@U^V1uhU#0$0aAOGL9ld;&F(^D5q7LOCPlaF0b*0XxtX3!L zbv{|_>tTE!W%eWx#>!c3`RG9iz}w*~Fj-(LwEkGCC|~Jm`Ve$DxT~G5Nm5*2NmaVu z^~D9^J6d$`Nac6O;C)PXQ+(LA2F`;NhK{0nX(5D+(lfTF+V>kY!zdl-gxE`;ixm)4 z(|5VQ2sBk62om*6+J^Qn8w;8cHnPVdohHL zsVJkN`-NSqt}#)njU-+Nm&~_Mkf$;}qzDeNAcy&tqZHlwzbu=?J3MIyaKm7;Z9-~Q zb)c}Qo(@t|WIR#C%kv$c{ThSz*e`C(YO8N&mFG&K&~jf?jw7sH>L6TES$NIs9s!|I zjCr9tS+_Em6#5A`fLnM%?xJzMJK1?CmSo(Mop_sAx%}gBz1Ln>orqy&fF#wHCMUII zPigaWnCdVbJiz`KxPZA5p9*T(qQcO}c&GDBw;ZdO%n5z%GS5haUa-kcPwsW>2}$_)N(A?;?VdRXCGiGCa+&!5C!5r z<6CF&ndbrOIhHD@xBP#Jiq!O;qIn|`r0Vym;R*cLKBKe@A;NbNV2`o{gaim}W7O=G7cV3P} z35o8*9>rg`$rDjwDm)Vy8Uy+}a5or((+$xn0oEZ7Fu^?#}Ls-Gy0laQUFao?N zM*dGr!$?Z<%ZH8lSL6PnqI|an&X#?HXBGhfG&|bU=uNA@C1DGu{&mf%rr=fbsi>j1 zrZ-}Ks-P&9`Dau7wMc+(V!#tKDr|zR8M)oBC~KaefVjhD@H&=@zl$h^h%Od7&!7tK zHU&16IzdOxr*Qe6LNS17wF^QR8n*9>($?h-f+aIOr2fODql0#rw`3M|k=ZfcZIK$& zVTAR?kx6dLy#hWr?+{HdDHZB?!jvouqlx_wUGksSu4N7V@KFdVktVSOoeSt9H2tyQ zR}k(+HGsq4oMdT(o|j0hn56WkoqrZ5YSYGMn!uem{0t}yuAU?Uh# z!-^{q;_QmSZe)6eLd=#`GF39i0-fWvTyu1PLHU4U1-a}lGAOq<_BDG52o91hlhVu z;-ir*UL=TO%ycz@@Z%?P={;BEqmwgFgR3aXN;@(Fys8u14P z>Y52X5_EHMeo!f#n{37s%$3;h1Uc4dh4>w)V1S`S`zN5r6jS*xm??4_*R$N0@Ta0arfc9>Fmlog$BCR*_3Tta@!LX% zpR>%xk=+nqQ@KfL_XN84gUmkb8Bajsul9PSPcwIT2HBI-JtdV&+j&fOJRhPqqBJ-o{Q!-qBu zW(V+idQbj?@~AI~T)yi2*z>FHc5-yn7s*yW{O}Vq!dLUpFIN-nX``6iqS{7!OfwgP z(8m&^iLFe{>{uIP3kz^JfLX%?yD-VY}azKzuRNS<SC$YoXSF6g6ZfLxVaSF0JZfAjRN zZw{y;<@EPSWv_f!Tu`2P*8G59#hclw;ztrSgQ3<=t{ReM0MhiKyn;C>0L*;DC8-pUNE0@Ho?2G6!2ev2` z;-(Yo#OV4OPd2x+a`B@%-W7a5oJUR9m80!Gd2PyB^_5@OSi_wLu|dvvt&O6e@H1|9 z-Wl4uvh3N+8w&!T%{2L+JR63e)Yg756k5MWzu=zj^%#8Iegq;c#_rjzOua-0mMv;1 zZN_3pGNbry*Y1N-c?e_vvjy(`Z*3Sk=Zq{{eT{BLpHvQ~d^fNClV|@dH#?q*r~W#4 z^K0`?N4$eyO(^kP7^v(wwj=B#%*b z>2c7e@vln?w#G^)P&jd%J=Q=uH_=!pQLtPgJt{nYWaOs$iET?Ib?k_%!qOHikqFy_H*@D?{sc@8sVL`BECEXBydk4|OS2QwX>J zY>_A&ulb;r?heNj#d=xd0k7fouHk9VN}i$Q8^7h|&?i1%=8 zI+K%;S>5o0PHtw}J2U>00;d$9v{ZNkUE-l}m zv&0M|ZJoY%;_oAqVon)bQ*S91nn-q%LDgPqsv(czPklYUM*K%<%2eJxX?OKedo9CE zTK>gjGp>@t(YPh~e48J#XMyp>r|rP`(O9mj=?K=iX~vr0H;Ya<1PYt`j|S}xFT5iNfj(t~W62+n>u;fz*w(BUvjieL z^3S~Aa~B65zO@zKRl2fUL<&EHrygs1kVo6fIHCoh|7Gt6?_O~y8)nV4W@HmmeV!da z1zozgt@Fk2wS4E13_!Uxvu^qB)xf2tENmI_EUeZC+2T4NP;U;u5vwX)9+VeyN7!of zkY1%Wk1_1QoGG*S#B8>(io3A-Jm^dCPhGQ@XTdR++j+d?@kDm`JdO6|a&i{wxRJD| zlnv{qm3I6D=eL@E+Qwvn$;*4L%l=a4clC4kh3*V-5k>{7B&?)|+@p1v3@S&Da zOxL&y*~y88qX$Q&w5*Ekc9%zM%fm?h?dh4v`e$bLF_l=q!vBa>pED^1t1{?i@m97O zNZ@@nrz9~)I$6%i<>yIbd*HZg#x~!eI+PaD%N-FUiF~sOdloF-^F3RL;tvK_o81?iqO#_d7IUtvp1{PMmXaqo3U`&)BB-PN3>m~a@v61_!e{(%SA0a?7|)rrfqP!V*-`=9 z$rG<*>XVfiM#RH6ak4TD)j!jrheh2`jNq9_nEldf;R~c-z9CWh&6ZI3sdCAp1G*8N zE!^$b9;W++Stl(R_4&~{w;jHaHmR_xIK1q2uLSr^^L>B%%By7uFX{!>|Ai7?v5*~H zRS*S7a~k3rNt+3FXiJaTMLU&sGtRk{EFamtFCJF;>!HH-C?(01cd>QD#O_5uwDu3A zZ9oUDZ1qN~u&M_4$0wQ9fTKy8C{EwM`blGPbWWy4%r;mHBO?g9*lJaAu2?+d9nhOX zfmA-s<%ng)9D^nuey%;EWA8eSuj47;r!u&% z0s$`(^Uyfrc;?03H|SK6Ie*f5w68G}Pe<$X5z3Op>q;)SM*l!;KA6x)^vry;Tz|yX z)BFwa{fwz%_xlezLA#oGwV-WX>WUDVU6~^QmT&0A2(Y$zaV5M=*qHD+z~f&Wo|1+R znM*_O&72~JXJY9C=ywOk0~`xMjn(?XiUF&)Mau&RwG7_^+?rV)>9C`X^7doY!;FEo zOY*H!!6P5E=Dvt1ZYq2;57ZCre<~R?v|NmD+F&%7kC5uVl;F^9;#pl*C+tR$OF$W>ILX`$#X)g|=Wm#60`KaBk*MVCXL`!b|H!Gk zZ{T@-7TQXnimH{$M2Vd`f|@eryK6lBH|3<0Uopmye^WR3+q&f)Xm-92ggUmtucQN- z{anC9NW_DtQPqHQh#|IIZEICWsfC@rlE#WXLN`f!*3^9!*&gmqU-u$@ssvhR|IQ(Y z>+M2&?6VzA;_m{zyt12HR{54VdX_f-N)5SIK3UrSo91C2k?%!+#QO8q3v+L1aAjIN z(9Vwax6sk~bVPfyt!sJum3f9o)wASc(Qt7Q{_C`PV5-*RJR!Q1@kcD%T1)~K%HY7I zZ$)@}+=`6_rSSx-`gHbVTY!O)szf@(0l#JAnmuF1p|;~%D%sQIuOoIqvFnu*FrD$b zT}h&|tFl5qXimEYVdtvwgNOE-k@}pnihXSAosc-mw{*tD?+V>TTndAF$bZa)66B1b zzwzakUM8aNY;^ni8my%yGYC<>ImQ-Leoc;@M)KSjZ5>B}Lpd~a^J{-cOwIj*m zu923_eZnnMCcj=Sw2}fzVm%i%md+bQuoFd9U+;u4p@~fRlvm>8)8_J?pUoPKCpTUY z*m>Rmm6hJQJYwGkM%%NC?AAOqk~!Nl_Cxsl)}vlAH%Ipf-sd!`e7eEKI@anIT49?a zflk~3ORvsPcw2s*2bTe0y;ImRxeU&1lP@aJ96som0imlJ3@}Ybi`N#{F04kT16iI2 zB&hg4PaYeir3mW@!}imj$*@_C7r=fgEu|<3cf_~tx>(Fhzpu{y^RU15>U&@EW0byb z^0@YYA~JZJ%yhY80!OCX>8K*SO1`OOy&Zjer+}&{_Xa5-?Wwwp9Ml~S9xg*JAf}bk zyI34-^V=RXcgF3EQR1H=u%v(1%iOQKqc{ z>WQacV!w1Yb&O39@*j_Lf57KUQN(iouLjfeF6H`o{~zOVe>MYx%n3mJ!%l}1x2OvO zH?PY4c0AR5&aicR8WGn**qF-`3%VN^&m!k}Tb=q$`)RRB)t5&MdWrO%#EDOtYv7)iGL7e*H(r>Kh*`L4zPR4Q0`@|$$1dPMi@ z+a;GKP_RFjbK428`%dw*-yFmOAQA{3LxsaF&HC)#9hKG&Ns8E)L;Rox;r;68bLhOayz}A`P1ps zt+e96*aYg+-?bRiY|^G<9ZrA;lNl92cXnyqx*wF^xSN}yFl=BEv69y_5)BK`XMT13 zoY{(8AGzHj}3TsEM(`jp}iEi>O7zEvWF&O!w@_zB7McVI~$DN zuiLP=pr@BZKD*I$-cNUyo7aQy_s=- z=15B~(XIo1Ub8mO&hKbM)?pY{7fBq##$}Oy5c3|6{PM;vD!uwOFAVG{r4$aaAxw16 zfDQWFK8pc8Ri{M$Pb|!K`^sk#cfJihWkv74zOr+HvhsBiSveRg7~2bko9-)<4r|co zMC1VC5|@q`9;H~cgB)DneX_x zAlsnOsry7xkVQ2&IR3%)X)4z2R6Pd8vO}W!EkCe)U)Yr1aT;cz0VNDuloRAD`a^~u z9jcds1=y=h{2=twJlW2Npz*K0Z|`>7vvix{xm|Fo5>UzW7I1;$VZbU?oD1+wA2?22`o!x7JVud5 zvJT4~LyK8sj4`+PWDmJAt_G9)XN8nW^b=;Es~ktsN|Jk3?GAkMHkrEo$9@Jx+*f@= za~eYX@zEgulBF+36lWivVSUPodDx?}Ky&a~BFP3(Nwm0s6LHRyqMAy!ya3+I;XS+m z=J`v=#IlOC+5^h|vEKK7e{#D&SR9gq(d`~@104TpR4%o%Rn3(-O+N8XRFD_*ijzwE z4x!UStCdr#hXP6naJZph7I*dzV2!H=;d|?<#bMqc z!<*U9A+wyrZe{1Gx`_L6%9`_0R`(Ez->zsxZ7Q4{giCQ|fBRBw73AdH<(%g}S0FNZ zCwt8BmvisQ-&?#rsUkj4z!MwEH=h{V)fjFf-FmRXC9qgDih=^};xesu0dM$1E)xAbTO;xj;s9MD6AJ$2aN2<_kPWB~^&O zn2Jp(Zl{VwyW5GY47h33;QV<3jvK|MGVZ!_WjJ2xDG&NT#TAU2gN2`%PSCOj!$S*QDu|YtNN7I{ zpbbs6e(=;f7rNbJfK8InkRlzZfm4LVPdRqBUst%!Dj|K)hA^(Q|6=iMnoFn+x1cBG z6N!EI`xN-J+poG23K@cL>A3X?T$`MmwmKpBwf98N;w9V~X3;9k9wNa>g@9y(KM z)$qfz0iA%Rnr<$+SP+0~6W$LbBBrW_RyrWV!RM!Z{P$ozhSCQDRu52a)ln|@P2r}R zK=epD)Ts0Aw|66DT98Y6(C>w!&xu*WP993zq_v_26-mm0B7KNj;MwWsT9va7ff{

    N?1#z#K31Yc@5g z+#>M{g5UZS6DPMJYN&{ZS2N&?4lS)a4G(sD(l|!=Qxa zAb1Z?znL(9nx))Zn;Y2Qw)GwoRs6>9wa_*Lrr-@Q6b*VN9>~l4&HwZ%({H{`3%w1p z1pyj?I<`6Gh$>#pi%HjUGO^;Vo>a&;Llw+Qh}ZCe9Bg+A8^v4yQ{|-l^~aSa*2M#9 z(`tWW{{p%bULD<-@xq~3VG7W{X*=6=qyrux`GCQPa;~l*k|pA>iTyPMv(u0Lwm(0Y zbrSU#%?F3T&+$!uz2h-H8Tlzy3ir-Ns3?1+)@UxBdjZb{`E(?+dv7*!aqf8=%y2lRsHWzT|m;G>}tb#LM}Gqh= zPy~n9tvVjw^L|9>{wMDR8 z1WQ%&tA?V!wzmcwvycT<(irLzx$z)E=Qc`h1?Q@T2K?RP&!$9}(F z6Nkf1o0iLPH6B4Lr|84pl8}_Ow1Vu(gEN!>u8xt|Ug|gQ$a~MwkK?TSn#lUX<4i3}1Q$sPXk^VZpnvHV=8yCAtl#jpDDRs)t8M$(bqs2(8ZeoOt& zXp*>%I7L=~aNCV*>?eUP|4z>zX(;{Q4^{t7|9G!{_1=M9bEWb4$#I)6^uNN~$!R=zMN;ea{>7jH^-kKFKd#u~{ zy?TFX(T^0OgxG_iRmz*Vl@D)cUr`QNRa1f-TmQUk$LfhHN-7RyW|othE>X_Qg!Y65 zGfL>sYY(1uvJMztXo`o&@x0A$I$A*+*TH^54|Mn4!34?wzb;tA(kO)s?ha2&(5*%KxgwdqdND4d=x9Fs@`NRc;G zVph{+W#|NeqPk?_2u#^H!JqxEtAmJfjsMJYw6)za0r@zFOFZIDWckDCM{wsR*Bto%<7*0531(^ zY%a6JEmO~-<+#2;0~}Zsr?1Qm_0e8VxI$OM4YpA|S(n(|Lr;@nR^nEd2pE<8>Pz#X zfNJ$emsN^$<-p%uCo|KL_Ls~1Mqbx${rT^lFQBDn!HN*KHe|BOCQOc@t@)-z?GyPyG=0Em%i7P;tup@e_U6!+AE&5#V$#ENM=oOi+7FKyS8 z1$_I!86RNTe0s=S2zj~ohquiNdKAk!m%QK93f;oW2Q~|jgq&> zQxEZ#5OoidI0A8j(*tf^718>vk`bc-+Y{$qlDSyPZp(!(6xUVjZy30Qi2_R8#m~;o z5l0}fLDQBrW9^P=W!b_FMgWF6Ivx{zlMj(Kb+w_yJ#EC7MEsuK9pXIG)HB;$UN=vom9_lrLOH`o^+}?a87sHoui50> z3z$0&Mbx7L!(xP9xG{a5pqF!N*hNiysYbiaK8z1ZR} zMY@y$df-*N5Pnc0)gY*`k}^P!(MW?4e}&dl;Z#|he<&=!yZ1+d0L%U&t@3pd`es3( zMa75!*_a~bMO4V%fkKKQ#sN`h^R7h1uTbRQ-+z_zD{Mi0D47;xTz*?Y*MchRuK1Mp zxrXoRqY2!u{2XkH+T-mobgdTK;eqUNfdzC4u#~|rN0?FI?BpT|fvdRyV_rHq8xo`0 zLX566f3@@JMF=39nxJP;@LXP&mkD@ISFUD&M+VBm!N@vk94!v*to@Ht0%bSKc*wtE z2~zATV+!W;EK;T>O{GZiFnpm?4mEYEfRWz@Z%wDYKi@V4jTCDjXLF#(MzTG_HB?g8 zIIuK@leoTF-L57p4+&aI$>|)t}4cP&<2pTAwBhFz*i+HB5Dws#s z0UZ3yMH$5v;%yQ=N?iM=ljn2i#5Qhp#jr|~ilqAktHVDv(#(6iq9Ugk(RhZ(G0S`D zj7L2vOOW!+UNhzyf)z+0&)yuD&uhP3G5YL=@VQa6mtFc3XGkimzWu{2YwMRp2W`1! zfp;sQ-j?u>H&oYLDw7v(KTeMRR~D9d`$lsTDA&m1-r1?_U( z@T-1n7E�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 z�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 Q0r&ymy{}QMZvE>204gbGQUCw| literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts new file mode 100644 index 000000000..f89cbd4c8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/lib/index.ts @@ -0,0 +1,128 @@ +/** + * 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 * 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 * as s3 from '@aws-cdk/aws-s3'; +import * as defaults from '@aws-solutions-konstruk/core'; +import { Construct } from '@aws-cdk/core'; + +/** + * The properties for the KinesisFirehoseToAnalyticsAndS3 class. + */ +export interface KinesisFirehoseToAnalyticsAndS3Props { + /** + * Optional user-provided props to override the default props for the Kinesis Firehose delivery stream. + * + * @default - Default props are used. + */ + readonly kinesisFirehoseProps?: kinesisFirehose.CfnDeliveryStreamProps | any; + /** + * Optional user-provided props to override the default props for the Kinesis Analytics application. + * + * @default - Default props are used. + */ + readonly kinesisAnalyticsProps?: kinesisAnalytics.CfnApplicationProps | any; + /** + * 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 +} + +/** + * @summary The KinesisFirehoseToAnalyticsAndS3 class. + */ +export class KinesisFirehoseToAnalyticsAndS3 extends Construct { + + // Declarations + private analytics: kinesisAnalytics.CfnApplication; + private kfs: KinesisFirehoseToS3; + + /** + * @summary Constructs a new instance of the KinesisFirehoseToAnalyticsAndS3 class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CloudFrontToApiGatewayProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: KinesisFirehoseToAnalyticsAndS3Props) { + super(scope, id); + + // Setup the kinesisfirehose-s3 pattern + const kinesisFirehoseToS3Props: KinesisFirehoseToS3Props = { + kinesisFirehoseProps: props.kinesisFirehoseProps, + deployBucket: props.deployBucket, + existingBucketObj: props.existingBucketObj, + bucketProps: props.bucketProps + }; + + // Add the kinesisfirehose-s3 pattern + this.kfs = new KinesisFirehoseToS3(this, 'KinesisFirehoseToS3', kinesisFirehoseToS3Props); + + // Add the Kinesis Analytics application + this.analytics = defaults.buildKinesisAnalyticsApp(this, { + kinesisFirehose: this.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(); + } +} \ 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-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json new file mode 100644 index 000000000..74fcbbb6d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/package.json @@ -0,0 +1,84 @@ +{ + "name": "@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics", + "version": "0.8.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" + }, + "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 run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.konstruk.services.kinesisfirehoses3kinesisanalytics", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "kinesisfirehoses3kinesisanalytics" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.KinesisFirehoseS3KinesisAnalytics", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kinesisanalytics": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kinesisanalytics": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-solutions-konstruk/aws-kinesisfirehose-s3": "~0.8.0" + } +} 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-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap new file mode 100644 index 000000000..f519732eb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/__snapshots__/test.kinesisfirehose-analytics-s3.test.js.snap @@ -0,0 +1,330 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pattern deployment w/ default properties 1`] = ` +Object { + "Resources": Object { + "testfirehoses3andanalyticsstackKinesisAnalytics20F3845E": Object { + "DependsOn": Array [ + "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F", + ], + "Properties": Object { + "Inputs": Array [ + Object { + "InputSchema": Object { + "RecordColumns": Array [ + Object { + "Mapping": "$.ticker_symbol", + "Name": "ticker_symbol", + "SqlType": "VARCHAR(4)", + }, + Object { + "Mapping": "$.sector", + "Name": "sector", + "SqlType": "VARCHAR(16)", + }, + Object { + "Mapping": "$.change", + "Name": "change", + "SqlType": "REAL", + }, + Object { + "Mapping": "$.price", + "Name": "price", + "SqlType": "REAL", + }, + ], + "RecordEncoding": "UTF-8", + "RecordFormat": Object { + "RecordFormatType": "JSON", + }, + }, + "KinesisFirehoseInput": Object { + "ResourceARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4", + "Arn", + ], + }, + "RoleARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC", + "Arn", + ], + }, + }, + "NamePrefix": "SOURCE_SQL_STREAM", + }, + ], + }, + "Type": "AWS::KinesisAnalytics::Application", + }, + "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "firehose:DescribeDeliveryStream", + "firehose:Get*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F", + "Roles": Array [ + Object { + "Ref": "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "kinesisanalytics.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4": Object { + "Properties": Object { + "ExtendedS3DestinationConfiguration": Object { + "BucketARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn", + ], + }, + "BufferingHints": Object { + "IntervalInSeconds": 300, + "SizeInMBs": 5, + }, + "CloudWatchLoggingOptions": Object { + "Enabled": true, + "LogGroupName": Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76", + }, + "LogStreamName": Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102", + }, + }, + "CompressionFormat": "GZIP", + "RoleARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA", + "Arn", + ], + }, + }, + }, + "Type": "AWS::KinesisFirehose::DeliveryStream", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehosePolicy8E134001": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:", + Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76", + }, + ":log-stream:", + Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehosePolicy8E134001", + "Roles": Array [ + Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "firehose.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354": 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": "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3LoggingBucket887A5000", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3LoggingBucket887A5000": 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", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "LogGroupName": Object { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76", + }, + }, + "Type": "AWS::Logs::LogStream", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..20b9723a6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.expected.json @@ -0,0 +1,326 @@ +{ + "Resources": { + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3LoggingBucket887A5000": { + "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" + } + ] + } + } + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3LoggingBucket887A5000" + } + }, + "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" + } + ] + } + } + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehosePolicy8E134001": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76" + }, + ":log-stream:", + { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehosePolicy8E134001", + "Roles": [ + { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA" + } + ] + } + }, + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4": { + "Type": "AWS::KinesisFirehose::DeliveryStream", + "Properties": { + "ExtendedS3DestinationConfiguration": { + "BucketARN": { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3S3BucketAE659354", + "Arn" + ] + }, + "BufferingHints": { + "IntervalInSeconds": 300, + "SizeInMBs": 5 + }, + "CloudWatchLoggingOptions": { + "Enabled": true, + "LogGroupName": { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroup7E569B76" + }, + "LogStreamName": { + "Ref": "testfirehoses3andanalyticsstackKinesisFirehoseToS3firehoseloggroupfirehoselogstream98C70102" + } + }, + "CompressionFormat": "GZIP", + "RoleARN": { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehoseRoleE7F8ADDA", + "Arn" + ] + } + } + } + }, + "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "kinesisanalytics.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "firehose:DescribeDeliveryStream", + "firehose:Get*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F", + "Roles": [ + { + "Ref": "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC" + } + ] + } + }, + "testfirehoses3andanalyticsstackKinesisAnalytics20F3845E": { + "Type": "AWS::KinesisAnalytics::Application", + "Properties": { + "Inputs": [ + { + "InputSchema": { + "RecordColumns": [ + { + "Mapping": "$.ticker_symbol", + "Name": "ticker_symbol", + "SqlType": "VARCHAR(4)" + }, + { + "Mapping": "$.sector", + "Name": "sector", + "SqlType": "VARCHAR(16)" + }, + { + "Mapping": "$.change", + "Name": "change", + "SqlType": "REAL" + }, + { + "Mapping": "$.price", + "Name": "price", + "SqlType": "REAL" + } + ], + "RecordEncoding": "UTF-8", + "RecordFormat": { + "RecordFormatType": "JSON" + } + }, + "KinesisFirehoseInput": { + "ResourceARN": { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisFirehoseToS3KinesisFirehose86F339C4", + "Arn" + ] + }, + "RoleARN": { + "Fn::GetAtt": [ + "testfirehoses3andanalyticsstackKinesisAnalyticsRole7217C4CC", + "Arn" + ] + } + }, + "NamePrefix": "SOURCE_SQL_STREAM" + } + ] + }, + "DependsOn": [ + "testfirehoses3andanalyticsstackKinesisAnalyticsPolicy2594304F" + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts new file mode 100644 index 000000000..ac3cd1476 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/integ.no-arguments.ts @@ -0,0 +1,57 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { KinesisFirehoseToAnalyticsAndS3, KinesisFirehoseToAnalyticsAndS3Props } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-firehose-s3-and-analytics-stack'); + +// Definitions +const props: KinesisFirehoseToAnalyticsAndS3Props = { + kinesisAnalyticsProps: { + inputs: [{ + inputSchema: { + recordColumns: [{ + name: 'ticker_symbol', + sqlType: 'VARCHAR(4)', + mapping: '$.ticker_symbol' + }, { + name: 'sector', + sqlType: 'VARCHAR(16)', + mapping: '$.sector' + }, { + name: 'change', + sqlType: 'REAL', + mapping: '$.change' + }, { + name: 'price', + sqlType: 'REAL', + mapping: '$.price' + }], + recordFormat: { + recordFormatType: 'JSON' + }, + recordEncoding: 'UTF-8' + }, + namePrefix: 'SOURCE_SQL_STREAM' + }] + } +}; + +new KinesisFirehoseToAnalyticsAndS3(stack, 'test-firehose-s3-and-analytics-stack', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js new file mode 100644 index 000000000..5844e65a2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/lambda/index.js @@ -0,0 +1,8 @@ +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `//stub//` + }; + }; \ No newline at end of file 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-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts new file mode 100644 index 000000000..3386d44d7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3-and-kinesisanalytics/test/test.kinesisfirehose-analytics-s3.test.ts @@ -0,0 +1,100 @@ +/** + * 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 { SynthUtils } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/core'; +import { KinesisFirehoseToAnalyticsAndS3, KinesisFirehoseToAnalyticsAndS3Props } from '../lib'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test Case 1 - Pattern deployment w/ default properties +// -------------------------------------------------------------- +test('Pattern deployment w/ default properties', () => { + // Initial Setup + const stack = new Stack(); + const props: KinesisFirehoseToAnalyticsAndS3Props = { + kinesisAnalyticsProps: { + inputs: [{ + inputSchema: { + recordColumns: [{ + name: 'ticker_symbol', + sqlType: 'VARCHAR(4)', + mapping: '$.ticker_symbol' + }, { + name: 'sector', + sqlType: 'VARCHAR(16)', + mapping: '$.sector' + }, { + name: 'change', + sqlType: 'REAL', + mapping: '$.change' + }, { + name: 'price', + sqlType: 'REAL', + mapping: '$.price' + }], + recordFormat: { + recordFormatType: 'JSON' + }, + recordEncoding: 'UTF-8' + }, + namePrefix: 'SOURCE_SQL_STREAM' + }] + } + }; + new KinesisFirehoseToAnalyticsAndS3(stack, 'test-firehose-s3-and-analytics-stack', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test Case 2 - Test the getter methods +// -------------------------------------------------------------- +test('Test getter methods', () => { + // Initial Setup + const stack = new Stack(); + const props: KinesisFirehoseToAnalyticsAndS3Props = { + kinesisFirehoseProps: { + deploy: true, + props: { + deliveryStreamName: "myDeliveryStream" + } + }, + kinesisAnalyticsProps: { + inputs: [{ + inputSchema: { + recordColumns: [{ + name: 'ts', + sqlType: 'TIMESTAMP', + mapping: '$.timestamp' + }, { + name: 'trip_id', + sqlType: 'VARCHAR(64)', + mapping: '$.trip_id' + }], + recordFormat: { + recordFormatType: 'JSON' + }, + recordEncoding: 'UTF-8' + }, + namePrefix: 'SOURCE_SQL_STREAM' + }] + } + }; + const app = new KinesisFirehoseToAnalyticsAndS3(stack, 'test-kinesis-firehose-kinesis-analytics', props); + // Assertions + expect(app.kinesisAnalytics()).toBeDefined(); + expect(app.kinesisFirehose()).toBeDefined(); + expect(app.bucket()).toBeDefined(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.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-kinesisfirehose-s3/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.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-kinesisfirehose-s3/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/.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-kinesisfirehose-s3/README.md b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md new file mode 100644 index 000000000..4c2f0aeb7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/README.md @@ -0,0 +1,69 @@ +# aws-kinesisfirehose-s3 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/aws-kinesisfirehose-s3/| +|:-------------|:-------------| +

    + +| **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`| + +This AWS Solutions Konstruk 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'); + +new KinesisFirehoseToS3(stack, 'test-firehose-s3', {}); + +``` + +## Initializer + +``` text +new KinesisFirehoseToS3(scope: Construct, id: string, props: KinesisFirehoseToS3Props); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`KinesisFirehoseToS3Props`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|kinesisFirehoseProps?|[`kinesisfirehose.CfnDeliveryStreamProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStreamProps.html)|Optional user provided props to override the default props for Kinesis Firehose Delivery Stream| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|kinesisFirehose()|[`kinesisfirehose.CfnDeliveryStream`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kinesisfirehose.CfnDeliveryStream.html)|Retruns 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)|Retruns an instance of s3.Bucket created by the construct| + +## 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-konstruk/aws-kinesisfirehose-s3/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..93dbc0c568f1c469d0215f482e9e6a5cc2ee82e5 GIT binary patch literal 115020 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

    ~|$LUJWg~+#&p}iKCX{{KCG+ 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts new file mode 100644 index 000000000..dd4988189 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/lib/index.ts @@ -0,0 +1,147 @@ +/** + * 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 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 iam from '@aws-cdk/aws-iam'; +import { overrideProps } from '@aws-solutions-konstruk/core'; +import * as logs from '@aws-cdk/aws-logs'; +import * as cdk from '@aws-cdk/core'; + +/** + * The properties for the KinesisFirehoseToS3 class. + */ +export interface KinesisFirehoseToS3Props { + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly kinesisFirehoseProps?: kinesisfirehose.CfnDeliveryStreamProps | any; + /** + * 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 +} + +export class KinesisFirehoseToS3 extends Construct { + + // Private variables + private firehose: kinesisfirehose.CfnDeliveryStream; + private s3Bucket: s3.Bucket; + + /** + * Constructs a new instance of the IotToLambda class. + */ + constructor(scope: Construct, id: string, props: KinesisFirehoseToS3Props) { + super(scope, id); + + // Setup S3 Bucket + this.s3Bucket = defaults.buildS3Bucket(this, { + deployBucket: props.deployBucket, + existingBucketObj: props.existingBucketObj, + bucketProps: props.bucketProps + }); + + // Extract the CfnBucket from the s3Bucket + const s3BucketResource = this.s3Bucket.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` + }] + } + }; + + // Setup Cloudwatch Log group & stream for Kinesis Firehose + const cwLogGroup: logs.LogGroup = new logs.LogGroup(this, 'firehose-log-group', defaults.DefaultLogGroupProps()); + const cwLogStream: logs.LogStream = cwLogGroup.addStream('firehose-log-stream'); + + // Setup the IAM Role for Kinesis Firehose + const firehoseRole = new iam.Role(this, 'KinesisFirehoseRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + + // Setup the IAM policy for Kinesis Firehose + const firehosePolicy = new iam.Policy(this, 'KinesisFirehosePolicy', { + statements: [new iam.PolicyStatement({ + actions: [ + 's3:AbortMultipartUpload', + 's3:GetBucketLocation', + 's3:GetObject', + 's3:ListBucket', + 's3:ListBucketMultipartUploads', + 's3:PutObject' + ], + resources: [`${this.s3Bucket.bucketArn}`, `${this.s3Bucket.bucketArn}/*`] + }), + new iam.PolicyStatement({ + actions: [ + 'logs:PutLogEvents' + ], + resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:${cwLogGroup.logGroupName}:log-stream:${cwLogStream.logStreamName}`] + }) + ]}); + + // Attach policy to role + firehosePolicy.attachToRole(firehoseRole); + + // Setup the default Kinesis Firehose props + const defaultKinesisFirehoseProps: kinesisfirehose.CfnDeliveryStreamProps = + defaults.DefaultCfnDeliveryStreamProps(this.s3Bucket.bucketArn, firehoseRole.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); + } else { + this.firehose = new kinesisfirehose.CfnDeliveryStream(this, 'KinesisFirehose', defaultKinesisFirehoseProps); + } + } + + /** + * Retruns an instance of kinesisfirehose.CfnDeliveryStream created by the construct + */ + public kinesisFirehose(): kinesisfirehose.CfnDeliveryStream { + return this.firehose; + } + + /** + * Retruns 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-konstruk/aws-kinesisfirehose-s3/package.json new file mode 100644 index 000000000..6379af08b --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/package.json @@ -0,0 +1,80 @@ +{ + "name": "@aws-solutions-konstruk/aws-kinesisfirehose-s3", + "version": "0.8.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" + }, + "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 run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.konstruk.services.kinesisfirehoses3", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "kinesisfirehoses3" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.KinesisFirehoseS3", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap new file mode 100644 index 000000000..77068fbc7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/__snapshots__/test.kinesisfirehose-s3.test.js.snap @@ -0,0 +1,229 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test KinesisFirehoseToS3 default params 1`] = ` +Object { + "Resources": Object { + "testfirehoses3KinesisFirehose5D459661": Object { + "Properties": Object { + "ExtendedS3DestinationConfiguration": Object { + "BucketARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3S3Bucket93480488", + "Arn", + ], + }, + "BufferingHints": Object { + "IntervalInSeconds": 300, + "SizeInMBs": 5, + }, + "CloudWatchLoggingOptions": Object { + "Enabled": true, + "LogGroupName": Object { + "Ref": "testfirehoses3firehoseloggroup8067C3EC", + }, + "LogStreamName": Object { + "Ref": "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B", + }, + }, + "CompressionFormat": "GZIP", + "RoleARN": Object { + "Fn::GetAtt": Array [ + "testfirehoses3KinesisFirehoseRole9BC5362D", + "Arn", + ], + }, + }, + }, + "Type": "AWS::KinesisFirehose::DeliveryStream", + }, + "testfirehoses3KinesisFirehosePolicy34C2972F": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "testfirehoses3S3Bucket93480488", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "testfirehoses3S3Bucket93480488", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:", + Object { + "Ref": "testfirehoses3firehoseloggroup8067C3EC", + }, + ":log-stream:", + Object { + "Ref": "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B", + }, + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testfirehoses3KinesisFirehosePolicy34C2972F", + "Roles": Array [ + Object { + "Ref": "testfirehoses3KinesisFirehoseRole9BC5362D", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testfirehoses3KinesisFirehoseRole9BC5362D": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "firehose.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "testfirehoses3S3Bucket93480488": 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": "testfirehoses3S3LoggingBucket31BFDC22", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "testfirehoses3S3LoggingBucket31BFDC22": 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", + }, + "testfirehoses3firehoseloggroup8067C3EC": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "LogGroupName": Object { + "Ref": "testfirehoses3firehoseloggroup8067C3EC", + }, + }, + "Type": "AWS::Logs::LogStream", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..d8ff5194c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.expected.json @@ -0,0 +1,226 @@ +{ + "Description": "Integration Test for aws-cdk-apl-kinesisfirehose-s3", + "Resources": { + "testfirehoses3S3LoggingBucket31BFDC22": { + "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" + } + ] + } + } + }, + "testfirehoses3S3Bucket93480488": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testfirehoses3S3LoggingBucket31BFDC22" + } + }, + "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" + } + ] + } + } + }, + "testfirehoses3firehoseloggroup8067C3EC": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B": { + "Type": "AWS::Logs::LogStream", + "Properties": { + "LogGroupName": { + "Ref": "testfirehoses3firehoseloggroup8067C3EC" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testfirehoses3KinesisFirehoseRole9BC5362D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "firehose.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testfirehoses3KinesisFirehosePolicy34C2972F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testfirehoses3S3Bucket93480488", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testfirehoses3S3Bucket93480488", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "logs:PutLogEvents", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "testfirehoses3firehoseloggroup8067C3EC" + }, + ":log-stream:", + { + "Ref": "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testfirehoses3KinesisFirehosePolicy34C2972F", + "Roles": [ + { + "Ref": "testfirehoses3KinesisFirehoseRole9BC5362D" + } + ] + } + }, + "testfirehoses3KinesisFirehose5D459661": { + "Type": "AWS::KinesisFirehose::DeliveryStream", + "Properties": { + "ExtendedS3DestinationConfiguration": { + "BucketARN": { + "Fn::GetAtt": [ + "testfirehoses3S3Bucket93480488", + "Arn" + ] + }, + "BufferingHints": { + "IntervalInSeconds": 300, + "SizeInMBs": 5 + }, + "CloudWatchLoggingOptions": { + "Enabled": true, + "LogGroupName": { + "Ref": "testfirehoses3firehoseloggroup8067C3EC" + }, + "LogStreamName": { + "Ref": "testfirehoses3firehoseloggroupfirehoselogstreamAC5E7A6B" + } + }, + "CompressionFormat": "GZIP", + "RoleARN": { + "Fn::GetAtt": [ + "testfirehoses3KinesisFirehoseRole9BC5362D", + "Arn" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts new file mode 100644 index 000000000..62158133e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/integ.no-arguments.ts @@ -0,0 +1,26 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { KinesisFirehoseToS3 } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-firehose-s3-stack'); +stack.templateOptions.description = 'Integration Test for aws-cdk-apl-kinesisfirehose-s3'; + +new KinesisFirehoseToS3(stack, 'test-firehose-s3', {}); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts new file mode 100644 index 000000000..e62d545ac --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisfirehose-s3/test/test.kinesisfirehose-s3.test.ts @@ -0,0 +1,113 @@ +/** + * 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 { 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) { + const props = {} as KinesisFirehoseToS3Props; + + return new KinesisFirehoseToS3(stack, 'test-firehose-s3', props); +} + +test('snapshot test KinesisFirehoseToS3 default params', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check s3Bucket default encryption', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResource('AWS::S3::Bucket', { + BucketEncryption: { + ServerSideEncryptionConfiguration: [{ + ServerSideEncryptionByDefault : { + SSEAlgorithm: "AES256" + } + }] + } + }); +}); + +test('check s3Bucket public access block configuration', () => { + const stack = new cdk.Stack(); + deploy(stack); + expect(stack).toHaveResource('AWS::S3::Bucket', { + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true + } + }); +}); + +test('test s3Bucket override publicAccessBlockConfiguration', () => { + const stack = new cdk.Stack(); + + new KinesisFirehoseToS3(stack, 'test-firehose-s3', { + bucketProps: { + blockPublicAccess: { + blockPublicAcls: false, + blockPublicPolicy: true, + ignorePublicAcls: false, + restrictPublicBuckets: true + } + } + }); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: false, + BlockPublicPolicy: true, + IgnorePublicAcls: false, + RestrictPublicBuckets: true + }, + }); +}); + +test('test kinesisFirehose override ', () => { + const stack = new cdk.Stack(); + + new KinesisFirehoseToS3(stack, 'test-firehose-s3', { + kinesisFirehoseProps: { + extendedS3DestinationConfiguration: { + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 55 + }, + } + } + }); + + expect(stack).toHaveResourceLike("AWS::KinesisFirehose::DeliveryStream", { + ExtendedS3DestinationConfiguration: { + BufferingHints: { + IntervalInSeconds: 600, + SizeInMBs: 55 + } + }}); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: KinesisFirehoseToS3 = deploy(stack); + + expect(construct.kinesisFirehose()).toBeInstanceOf(kinesisfirehose.CfnDeliveryStream); + expect(construct.bucket()).toBeDefined(); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-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-kinesisstreams-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-kinesisstreams-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-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-kinesisstreams-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md new file mode 100644 index 000000000..d70f9de0d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/README.md @@ -0,0 +1,82 @@ +# aws-kinesisstreams-lambda 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/aws-kinesisstreams-lambda/| +|:-------------|:-------------| +

    + +| **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`| + +This AWS Solutions Konstruk 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'); + +new KinesisStreamsToLambda(stack, 'KinesisToLambdaPattern', { + deployLambda: true, + eventSourceProps: { + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + batchSize: 1 + }, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}); + +``` + +## Initializer + +``` text +new KinesisStreamsToLambda(scope: Construct, id: string, props: KinesisStreamsToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`KinesisStreamsToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function. If set to false, you must provide an existing function for the `existingLambdaObj` property.| +|existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|An optional, existing Lambda function. This property is required if `deployLambda` is set to false.| +|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.| + +## 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-konstruk/aws-kinesisstreams-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..fab430632cdd2e5c0162220d31a9c78b62f9d79e GIT binary patch literal 89950 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
    5Dne9h{v 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
  1. 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 AQvd(} literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts new file mode 100644 index 000000000..544a81b5f --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/lib/index.ts @@ -0,0 +1,167 @@ +/** + * 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 * 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 { Construct } from '@aws-cdk/core'; + +/** + * The properties for the KinesisStreamsToLambda class. + */ +export interface KinesisStreamsToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide an existing function for the `existingLambdaObj` property. + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * An optional, existing Lambda function. + * This property is required if `deployLambda` is set to false. + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user-provided props to override the default props for the Lambda function. + * This property is only required if `deployLambda` is set to true. + * + * @default - Default props are used. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any + /** + * Optional user-provided props to override the default props for the Kinesis stream. + * + * @default - Default props are used. + */ + readonly kinesisStreamProps?: kinesis.StreamProps | any + /** + * Optional user-provided props to override the default props for the Lambda event source mapping. + * + * @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; + + /** + * @summary Constructs a new instance of the KinesisStreamsToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CloudFrontToApiGatewayProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: KinesisStreamsToLambdaProps) { + super(scope, id); + + // Setup the encryption key + this.encryptionKey = defaults.buildEncryptionKey(scope, { + encryptionKeyProps: props.encryptionKeyProps + }); + + // Setup the Kinesis Stream + this.kinesisStream = defaults.buildKinesisStream(scope, { + encryptionKey: this.encryptionKey, + kinesisStreamProps: props.kinesisStreamProps + }); + + // Setup the Lambda function + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Add the Lambda event source mapping + const eventSourceProps = overrideProps(defaults.DefaultKinesisEventSourceProps, props.eventSourceProps); + eventSourceProps.eventSourceArn = this.kinesisStream.streamArn; + eventSourceProps.functionName = this.fn.functionName; + this.fn.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; + policy.addStatements(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + resources: [ this.kinesisStream.streamArn ], + actions: [ + 'kinesis:GetRecords', + 'kinesis:GetShardIterator', + 'kinesis:DescribeStream' + ] + })); + policy.addStatements(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + resources: [ '*' ], + actions: [ + 'kinesis:ListStreams', + ] + })); + policy.attachToRole(role); + this.kinesisStream.grantRead(this.fn.grantPrincipal); + + // Add appropriate cfn_nag metadata + const cfnCustomPolicy = policy.node.defaultChild as iam.CfnPolicy; + cfnCustomPolicy.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [ + { + id: "W12", + reason: "The kinesis:ListStreams action requires a wildcard resource." + } + ] + } + }; + } + + /** + * @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-konstruk/aws-kinesisstreams-lambda/package.json new file mode 100644 index 000000000..6e65b26bb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/package.json @@ -0,0 +1,81 @@ +{ + "name": "@aws-solutions-konstruk/aws-kinesisstreams-lambda", + "version": "0.8.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" + }, + "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.kinesisstreamslambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "kinesisstreamslambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.KinesisStreamsLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap new file mode 100644 index 000000000..7fdd4bd79 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/__snapshots__/test.kinesisstreams-lambda.test.js.snap @@ -0,0 +1,325 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pattern deployment 1`] = ` +Object { + "Parameters": Object { + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdArtifactHashEA3A5944": Object { + "Description": "Artifact hash for asset \\"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\\"", + "Type": "String", + }, + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3BucketA460830B": Object { + "Description": "S3 bucket for asset \\"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\\"", + "Type": "String", + }, + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6": Object { + "Description": "S3 key for asset version \\"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\\"", + "Type": "String", + }, + }, + "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": "*", + }, + Object { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "KinesisStream46752A3E": Object { + "Properties": Object { + "RetentionPeriodHours": 24, + "ShardCount": 1, + "StreamEncryption": Object { + "EncryptionType": "KMS", + "KeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Kinesis::Stream", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3BucketA460830B", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionLambdaKinesisEventSourceMappingE4E81D45": Object { + "Properties": Object { + "BatchSize": 1, + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "KinesisStream46752A3E", + "Arn", + ], + }, + "FunctionName": Object { + "Ref": "LambdaFunctionBF21E41F", + }, + "StartingPosition": "TRIM_HORIZON", + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kinesis:DescribeStream", + "kinesis:GetRecords", + "kinesis:GetShardIterator", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "KinesisStream46752A3E", + "Arn", + ], + }, + }, + Object { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testkinesisstreamslambdaLambdaFunctionPolicyF7EF016E": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "The kinesis:ListStreams action requires a wildcard resource.", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "kinesis:GetRecords", + "kinesis:GetShardIterator", + "kinesis:DescribeStream", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "KinesisStream46752A3E", + "Arn", + ], + }, + }, + Object { + "Action": "kinesis:ListStreams", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testkinesisstreamslambdaLambdaFunctionPolicyF7EF016E", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json new file mode 100644 index 000000000..f0cce5a64 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.expected.json @@ -0,0 +1,322 @@ +{ + "Description": "Integration Test for aws-kinesisstreams-lambda", + "Resources": { + "testkslambdaLambdaFunctionPolicyDC40446F": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kinesis:GetRecords", + "kinesis:GetShardIterator", + "kinesis:DescribeStream" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KinesisStream46752A3E", + "Arn" + ] + } + }, + { + "Action": "kinesis:ListStreams", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testkslambdaLambdaFunctionPolicyDC40446F", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "The kinesis:ListStreams action requires a wildcard resource." + } + ] + } + } + }, + "EncryptionKey1B843E66": { + "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" + }, + "KinesisStream46752A3E": { + "Type": "AWS::Kinesis::Stream", + "Properties": { + "ShardCount": 1, + "RetentionPeriodHours": 24, + "StreamEncryption": { + "EncryptionType": "KMS", + "KeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + } + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kinesis:DescribeStream", + "kinesis:GetRecords", + "kinesis:GetShardIterator" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KinesisStream46752A3E", + "Arn" + ] + } + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3BucketA460830B" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "LambdaFunctionLambdaKinesisEventSourceMappingE4E81D45": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "KinesisStream46752A3E", + "Arn" + ] + }, + "FunctionName": { + "Ref": "LambdaFunctionBF21E41F" + }, + "BatchSize": 1, + "StartingPosition": "TRIM_HORIZON" + } + } + }, + "Parameters": { + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3BucketA460830B": { + "Type": "String", + "Description": "S3 bucket for asset \"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\"" + }, + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdS3VersionKey58FEB9E6": { + "Type": "String", + "Description": "S3 key for asset version \"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\"" + }, + "AssetParametersdfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcdArtifactHashEA3A5944": { + "Type": "String", + "Description": "Artifact hash for asset \"dfe828a7d00b0da7a6e92dc1decf39ec907e4edc6006faea8631d4dabd7f4fcd\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts new file mode 100644 index 000000000..d04854a6d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/integ.deployFunction.ts @@ -0,0 +1,43 @@ +/** + * 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 { KinesisStreamsToLambda, KinesisStreamsToLambdaProps } from '../lib'; +import { Stack, App } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-ks-lambda-stack'); +stack.templateOptions.description = 'Integration Test for aws-kinesisstreams-lambda'; + +// Definitions +const props: KinesisStreamsToLambdaProps = { + deployLambda: true, + encryptionKeyProps: {}, + kinesisStreamProps: {}, + eventSourceProps: { + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + batchSize: 1 + }, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, +}; + +new KinesisStreamsToLambda(stack, 'test-ks-lambda', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js new file mode 100644 index 000000000..f63f72c0c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/lambda/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `//stub//` + }; +}; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts new file mode 100644 index 000000000..851b35768 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-kinesisstreams-lambda/test/test.kinesisstreams-lambda.test.ts @@ -0,0 +1,72 @@ +/** + * 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 { KinesisStreamsToLambda, KinesisStreamsToLambdaProps } from "../lib"; +import { StartingPosition } from '@aws-cdk/aws-lambda'; +import { SynthUtils } from '@aws-cdk/assert'; +import * as lambda from '@aws-cdk/aws-lambda'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Pattern deployment +// -------------------------------------------------------------- +test('Pattern deployment', () => { + // Initial setup + const stack = new Stack(); + const props: KinesisStreamsToLambdaProps = { + encryptionKeyProps: {}, + kinesisStreamProps: {}, + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + eventSourceProps: { + startingPosition: StartingPosition.TRIM_HORIZON, + batchSize: 1 + } + }; + new KinesisStreamsToLambda(stack, 'test-kinesis-streams-lambda', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test getter methods +// -------------------------------------------------------------- +test('Test getter methods', () => { + // Initial Setup + const stack = new Stack(); + const props: KinesisStreamsToLambdaProps = { + encryptionKeyProps: {}, + kinesisStreamProps: {}, + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + eventSourceProps: { + startingPosition: StartingPosition.TRIM_HORIZON, + batchSize: 1 + } + }; + const app = new KinesisStreamsToLambda(stack, 'test-kinesis-streams-lambda', props); + // Assertion 1 + expect(app.lambdaFunction()).toBeDefined(); + // Assertion 2 + expect(app.stream()).toBeDefined(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.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-lambda-dynamodb/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.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-lambda-dynamodb/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/.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-lambda-dynamodb/README.md b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md new file mode 100644 index 000000000..58e0f28df --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/README.md @@ -0,0 +1,78 @@ +# aws-lambda-dynamodb 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/aws-lambda-dynamodb/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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 props: LambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, +}; + +new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); + +``` + +## Initializer + +``` text +new LambdaToDynamoDB(scope: Construct, id: string, props: LambdaToDynamoDBProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`LambdaToDynamoDBProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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| +|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| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Retruns 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)|Retruns an instance of dynamodb.Table created by the construct| + +## 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-konstruk/aws-lambda-dynamodb/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..710b68e54b6750235bee7b9199a247c08151971c GIT binary patch literal 71847 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
    vD6$*!l30_^NHYldAj&pD#lK4PY- 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts new file mode 100644 index 000000000..8832735ec --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/lib/index.ts @@ -0,0 +1,107 @@ +/** + * 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 lambda from '@aws-cdk/aws-lambda'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as defaults from '@aws-solutions-konstruk/core'; +import { Construct } from '@aws-cdk/core'; +import { overrideProps } from '@aws-solutions-konstruk/core'; + +/** + * @summary The properties for the LambdaToDynamoDB Construct + */ +export interface LambdaToDynamoDBProps { + /** + * Whether to create a new lambda function or use an existing lambda function. + * If set to false, you must provide a lambda function object as `existingObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * 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 lambdaFunctionProps?: lambda.FunctionProps, + /** + * Optional user provided props to override the default props + * + * @default - Default props are used + */ + readonly dynamoTableProps?: dynamodb.TableProps +} + +export class LambdaToDynamoDB extends Construct { + private fn: lambda.Function; + private table: dynamodb.Table; + + /** + * @summary Constructs a new instance of the LambdaToDynamoDB class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {LambdaToDynamoDBProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: LambdaToDynamoDBProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // 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); + } else { + this.table = new dynamodb.Table(this, 'DynamoTable', defaults.DefaultTableProps); + } + + this.fn.addEnvironment('DDB_TABLE_NAME', this.table.tableName); + + this.table.grantReadWriteData(this.fn.grantPrincipal); + } + + /** + * @summary Retruns 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 Retruns 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-konstruk/aws-lambda-dynamodb/package.json new file mode 100644 index 000000000..cb43a8a40 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/package.json @@ -0,0 +1,77 @@ +{ + "name": "@aws-solutions-konstruk/aws-lambda-dynamodb", + "version": "0.8.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" + }, + "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.lambdadynamodb", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "lambdadynamodb" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.LambdaDynamodb", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap new file mode 100644 index 000000000..5c8fbe976 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/__snapshots__/lambda-dynamodb.test.js.snap @@ -0,0 +1,211 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test LambdaToDynamoDB default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": Object { + "Description": "Artifact hash for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": Object { + "Description": "S3 bucket for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": Object { + "Description": "S3 key for asset version \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": Object { + "Ref": "testlambdadynamodbstackDynamoTable8138E93B", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testlambdadynamodbstackDynamoTable8138E93B": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "id", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "id", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json new file mode 100644 index 000000000..7f5c82981 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.expected.json @@ -0,0 +1,236 @@ +{ + "Resources": { + "testlambdadynamodbstackDynamoTable8138E93B": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + }, + { + "AttributeName": "id2", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "GlobalSecondaryIndexes": [ + { + "IndexName": "test_id2", + "KeySchema": [ + { + "AttributeName": "id2", + "KeyType": "HASH" + } + ], + "Projection": { + "ProjectionType": "ALL" + } + } + ], + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + "/index/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": { + "Ref": "testlambdadynamodbstackDynamoTable8138E93B" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts new file mode 100644 index 000000000..127960e45 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.add-secondary-index.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToDynamoDB } from "../lib"; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Change the billing mode to PROVISIONED +const stack = new Stack(app, 'test-lambda-dynamodb-stack'); + +const construct: LambdaToDynamoDB = new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, +}); + +const props: dynamodb.GlobalSecondaryIndexProps = { + partitionKey: { + name: 'id2', + type: dynamodb.AttributeType.STRING + }, + indexName: 'test_id2' +}; +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-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..a95e1b63f --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.expected.json @@ -0,0 +1,207 @@ +{ + "Resources": { + "testlambdadynamodbstackDynamoTable8138E93B": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": { + "Ref": "testlambdadynamodbstackDynamoTable8138E93B" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts new file mode 100644 index 000000000..26a4e3048 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.no-arguments.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToDynamoDB, LambdaToDynamoDBProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Empty arguments +const stack = new Stack(app, 'test-lambda-dynamodb-stack'); + +const props: LambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, +}; + +new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json new file mode 100644 index 000000000..587990468 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.expected.json @@ -0,0 +1,210 @@ +{ + "Resources": { + "testlambdadynamodbstackDynamoTable8138E93B": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 3, + "WriteCapacityUnits": 3 + }, + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": { + "Ref": "testlambdadynamodbstackDynamoTable8138E93B" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts new file mode 100644 index 000000000..9e38e89dc --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.set-billing-mode.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToDynamoDB } from "../lib"; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Change the billing mode to PROVISIONED +const stack = new Stack(app, 'test-lambda-dynamodb-stack'); + +new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', { + dynamoTableProps: { + billingMode: dynamodb.BillingMode.PROVISIONED, + readCapacity: 3, + writeCapacity: 3, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + } + }, + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + } +}); + +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json new file mode 100644 index 000000000..499220ebc --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.expected.json @@ -0,0 +1,207 @@ +{ + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": { + "Ref": "testlambdadynamodbstackDynamoTable8138E93B" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "testlambdadynamodbstackDynamoTable8138E93B": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts new file mode 100644 index 000000000..203a9cef5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/integ.use-existing-func.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +/// !cdk-integ * +import { App, Stack } from "@aws-cdk/core"; +import { LambdaToDynamoDB } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '@aws-solutions-konstruk/core'; + +const app = new App(); +const stack = new Stack(app, 'test-lambda-dynamodb-stack'); +const lambdaFunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) +}; + +const func = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + +new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', { + deployLambda: false, + existingLambdaObj: func +}); + +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts new file mode 100644 index 000000000..8952d2223 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda-dynamodb.test.ts @@ -0,0 +1,307 @@ +/** + * 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 { LambdaToDynamoDB, LambdaToDynamoDBProps } 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 '@aws-cdk/assert/jest'; + +function deployNewFunc(stack: cdk.Stack) { + const props: LambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + }; + + return new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); +} + +function useExistingFunc(stack: cdk.Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const props: LambdaToDynamoDBProps = { + deployLambda: false, + existingLambdaObj: new lambda.Function(stack, 'MyExistingFunction', lambdaFunctionProps), + dynamoTableProps: { + billingMode: dynamodb.BillingMode.PROVISIONED, + readCapacity: 3, + writeCapacity: 3, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + } + }, + }; + + return new LambdaToDynamoDB(stack, 'test-lambda-dynamodb-stack', props); +} + +test('snapshot test LambdaToDynamoDB default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check lambda function properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs10.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", + DDB_TABLE_NAME: { + Ref: "testlambdadynamodbstackDynamoTable8138E93B" + } + } + } + }); +}); +test('check dynamo table properties for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::DynamoDB::Table', { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + } + ], + BillingMode: "PAY_PER_REQUEST", + SSESpecification: { + SSEEnabled: true + } + }); +}); +test('check iot lambda function role for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + 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" + } + ] + }); +}); +test('check lambda function policy for deploy: true', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + Effect: "Allow", + Resource: [ + { + "Fn::GetAtt": [ + "testlambdadynamodbstackDynamoTable8138E93B", + "Arn" + ] + }, + { + Ref: "AWS::NoValue" + } + ] + } + ], + Version: "2012-10-17" + } + }); +}); +test('check lambda function properties for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "MyExistingFunctionServiceRoleF9E14BFD", + "Arn" + ] + }, + Runtime: "python3.6" + }); +}); +test('check iot lambda function role for deploy: false', () => { + const stack = new cdk.Stack(); + + useExistingFunc(stack); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRole", + Effect: "Allow", + Principal: { + Service: "lambda.amazonaws.com" + } + } + ], + Version: "2012-10-17" + }, + ManagedPolicyArns: [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + Ref: "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + }); +}); +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: LambdaToDynamoDB = deployNewFunc(stack); + + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.dynamoTable()).toBeInstanceOf(dynamodb.Table); +}); +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: LambdaToDynamoDBProps = { + deployLambda: true + }; + + try { + new LambdaToDynamoDB(stack, 'test-iot-lambda-integration', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); +test('check deploy = true and no prop', () => { + const stack = new cdk.Stack(); + + const props: LambdaToDynamoDBProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + } + }; + new LambdaToDynamoDB(stack, 'test-iot-lambda-stack', props); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs10.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", + DDB_TABLE_NAME: { + Ref: "testiotlambdastackDynamoTable76858356" + } + } + } + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js new file mode 100644 index 000000000..743e4fdbb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-dynamodb/test/lambda/index.js @@ -0,0 +1,8 @@ +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `Hello, CDK! You've hit ${event.path}\n` + }; + }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.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-lambda-elasticsearch-kibana/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.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-lambda-elasticsearch-kibana/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/.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-lambda-elasticsearch-kibana/README.md b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md new file mode 100644 index 000000000..5ebec61d8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/README.md @@ -0,0 +1,84 @@ +# aws-lambda-elasticsearch-kibana 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/aws-lambda-elasticsearch-kibana/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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 lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: lambdaProps, + deployLambda: true, + domainName: 'test-domain' +}); + +``` + +## Initializer + +``` text +new LambdaToElasticSearchAndKibana(scope: Construct, id: string, props: LambdaToElasticSearchAndKibanaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`LambdaToElasticSearchAndKibanaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function| +|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| +|esDomainProps?|[`elasticsearch.CfnDomainProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-elasticsearch.CfnDomainProps.html)|Optional user provided props to override the default props for the Elasticsearch Service| +|domainName|`string`|Domain name for the Cognito and the Elasticsearch Service| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|lambdaFunction()|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns 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)|Retruns a list of cloudwatch.Alarm created by the construct| + +## 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-konstruk/aws-lambda-elasticsearch-kibana/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..23ea4805900a2fa687967b26a1d0f883200f33be GIT binary patch literal 96006 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 B@&*6^ literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts new file mode 100644 index 000000000..7fe41e234 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/lib/index.ts @@ -0,0 +1,171 @@ +/** + * 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 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 { Construct } from '@aws-cdk/core'; +import { Role } from '@aws-cdk/aws-iam'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +/** + * @summary The properties for the CognitoToApiGatewayToLambda Construct + */ +export interface LambdaToElasticSearchAndKibanaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide a lambda function object as `existingLambdaObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided props to override the default props for the Lambda function. + * If `deploy` is set to true only then this property is required + * + * @default - Default props are used + */ + readonly lambdaFunctionProps?: lambda.FunctionProps + /** + * Optional user provided props to override the default props for the Elasticsearch Service. + * + * @default - Default props are used + */ + readonly esDomainProps?: elasticsearch.CfnDomainProps, + /** + * Cognito & ES Domain Name + * + * @default - None + */ + readonly domainName: string +} + +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[]; + + /** + * @summary Constructs a new instance of the CognitoToApiGatewayToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CognitoToApiGatewayToLambdaProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: LambdaToElasticSearchAndKibanaProps) { + super(scope, id); + + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Find the lambda service Role ARN + const lambdaFunctionRoleARN = this.fn.role?.roleArn; + + this.userpool = defaults.buildUserPool(scope); + this.userpoolclient = defaults.buildUserPoolClient(scope, this.userpool); + this.identitypool = defaults.buildIdentityPool(scope, this.userpool, this.userpoolclient); + + const cognitoAuthorizedRole: Role = defaults.setupCognitoForElasticSearch(scope, props.domainName, { + userpool: this.userpool, + identitypool: this.identitypool, + userpoolclient: this.userpoolclient + }); + + this.elasticsearch = defaults.buildElasticSearch(scope, 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); + + // Deploy best practices CW Alarms for ES + this.cwAlarms = defaults.buildElasticSearchCWAlarms(scope); + } + + /** + * @summary Retruns 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 Retruns 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 Retruns 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 Retruns 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 Retruns 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 Retruns 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; + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json new file mode 100644 index 000000000..3ac3db569 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/package.json @@ -0,0 +1,83 @@ +{ + "name": "@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana", + "version": "0.8.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" + }, + "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.lambdaelasticsearchkibana", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "lambdaelasticsearchkibana" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.LambdaElasticsearchKibana", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0" + } +} 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-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap new file mode 100644 index 000000000..9380edc79 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/__snapshots__/lambda-elasticsearch-kibana.test.js.snap @@ -0,0 +1,590 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbArtifactHash322F5E2F": Object { + "Description": "Artifact hash for asset \\"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\\"", + "Type": "String", + }, + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3BucketBAF5BF3A": Object { + "Description": "S3 bucket for asset \\"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\\"", + "Type": "String", + }, + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3": Object { + "Description": "S3 key for asset version \\"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\\"", + "Type": "String", + }, + }, + "Resources": Object { + "AutomatedSnapshotFailureTooHighAlarmA7918D4F": Object { + "Properties": Object { + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "CPUUtilizationTooHighAlarmA395C469": Object { + "Properties": Object { + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "CognitoAuthorizedRole14E74FE0": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": Object { + "ForAnyValue:StringLike": Object { + "cognito-identity.amazonaws.com:amr": "authenticated", + }, + "StringEquals": Object { + "cognito-identity.amazonaws.com:aud": Object { + "Ref": "CognitoIdentityPool", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoAccessPolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CognitoIdentityPool": Object { + "Properties": Object { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": Array [ + Object { + "ClientId": Object { + "Ref": "CognitoUserPoolClient5AB59AE4", + }, + "ProviderName": Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "ProviderName", + ], + }, + "ServerSideTokenCheck": true, + }, + ], + }, + "Type": "AWS::Cognito::IdentityPool", + }, + "CognitoKibanaConfigureRole62CCE76A": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "es.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "CognitoKibanaConfigureRolePolicy76F46A5E": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:cognito-identity:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":identitypool/", + Object { + "Ref": "CognitoIdentityPool", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain", + ], + ], + }, + ], + }, + Object { + "Action": "iam:PassRole", + "Condition": Object { + "StringLike": Object { + "iam:PassedToService": "cognito-identity.amazonaws.com", + }, + }, + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoKibanaConfigureRolePolicy76F46A5E", + "Roles": Array [ + Object { + "Ref": "CognitoKibanaConfigureRole62CCE76A", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + "ElasticsearchDomain": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only", + }, + ], + }, + }, + "Properties": Object { + "AccessPolicies": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": Object { + "AWS": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + ], + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "CognitoOptions": Object { + "Enabled": true, + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "DomainName": "test-domain", + "EBSOptions": Object { + "EBSEnabled": true, + "VolumeSize": 10, + }, + "ElasticsearchClusterConfig": Object { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": Object { + "AvailabilityZoneCount": 3, + }, + "ZoneAwarenessEnabled": true, + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": Object { + "Enabled": true, + }, + "NodeToNodeEncryptionOptions": Object { + "Enabled": true, + }, + "SnapshotOptions": Object { + "AutomatedSnapshotStartHour": 1, + }, + }, + "Type": "AWS::Elasticsearch::Domain", + }, + "FreeStorageSpaceTooLowAlarm3410CBE2": Object { + "Properties": Object { + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 2000, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "IdentityPoolRoleMapping": Object { + "Properties": Object { + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "Roles": Object { + "authenticated": Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + }, + "IndexWritesBlockedTooHighAlarm5F7E9A55": Object { + "Properties": Object { + "AlarmDescription": "Your cluster is blocking write requests.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "JVMMemoryPressureTooHighAlarm303EEA7C": Object { + "Properties": Object { + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3BucketBAF5BF3A", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DOMAIN_ENDPOINT": Object { + "Fn::GetAtt": Array [ + "ElasticsearchDomain", + "DomainEndpoint", + ], + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "MasterCPUUtilizationTooHighAlarm1CE1084B": Object { + "Properties": Object { + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "MasterJVMMemoryPressureTooHighAlarmBB15F770": Object { + "Properties": Object { + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "StatusRedAlarm4CE918C2": Object { + "Properties": Object { + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "StatusYellowAlarm2B20F083": Object { + "Properties": Object { + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1, + }, + "Type": "AWS::CloudWatch::Alarm", + }, + "UserPoolDomain": Object { + "DependsOn": Array [ + "CognitoUserPool53E37E69", + ], + "Properties": Object { + "Domain": "test-domain", + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolDomain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..63c0bae3e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.expected.json @@ -0,0 +1,586 @@ +{ + "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": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3BucketBAF5BF3A" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DOMAIN_ENDPOINT": { + "Fn::GetAtt": [ + "ElasticsearchDomain", + "DomainEndpoint" + ] + } + } + } + }, + "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." + } + ] + } + } + }, + "CognitoUserPool53E37E69": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "LambdaConfig": {}, + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + } + } + }, + "CognitoUserPoolClient5AB59AE4": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + } + }, + "CognitoIdentityPool": { + "Type": "AWS::Cognito::IdentityPool", + "Properties": { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": [ + { + "ClientId": { + "Ref": "CognitoUserPoolClient5AB59AE4" + }, + "ProviderName": { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "ProviderName" + ] + }, + "ServerSideTokenCheck": true + } + ] + } + }, + "UserPoolDomain": { + "Type": "AWS::Cognito::UserPoolDomain", + "Properties": { + "Domain": "test-domain1", + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + }, + "DependsOn": [ + "CognitoUserPool53E37E69" + ] + }, + "CognitoAuthorizedRole14E74FE0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + "Ref": "CognitoIdentityPool" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + "Effect": "Allow", + "Principal": { + "Federated": "cognito-identity.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/test-domain1/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoAccessPolicy" + } + ] + } + }, + "IdentityPoolRoleMapping": { + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + "Properties": { + "IdentityPoolId": { + "Ref": "CognitoIdentityPool" + }, + "Roles": { + "authenticated": { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + } + } + } + }, + "CognitoKibanaConfigureRole62CCE76A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "CognitoKibanaConfigureRolePolicy76F46A5E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:cognito-identity:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":identitypool/", + { + "Ref": "CognitoIdentityPool" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/test-domain1" + ] + ] + } + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "cognito-identity.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CognitoKibanaConfigureRolePolicy76F46A5E", + "Roles": [ + { + "Ref": "CognitoKibanaConfigureRole62CCE76A" + } + ] + } + }, + "ElasticsearchDomain": { + "Type": "AWS::Elasticsearch::Domain", + "Properties": { + "AccessPolicies": { + "Statement": [ + { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": { + "AWS": [ + { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + } + ] + }, + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":domain/test-domain1/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "CognitoOptions": { + "Enabled": true, + "IdentityPoolId": { + "Ref": "CognitoIdentityPool" + }, + "RoleArn": { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + }, + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + }, + "DomainName": "test-domain1", + "EBSOptions": { + "EBSEnabled": true, + "VolumeSize": 10 + }, + "ElasticsearchClusterConfig": { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": { + "AvailabilityZoneCount": 3 + }, + "ZoneAwarenessEnabled": true + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": { + "Enabled": true + }, + "NodeToNodeEncryptionOptions": { + "Enabled": true + }, + "SnapshotOptions": { + "AutomatedSnapshotStartHour": 1 + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only" + } + ] + } + } + }, + "StatusRedAlarm4CE918C2": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one primary shard and its replicas are not allocated to a node. ", + "MetricName": "ClusterStatus.red", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "StatusYellowAlarm2B20F083": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "At least one replica shard is not allocated to a node.", + "MetricName": "ClusterStatus.yellow", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "FreeStorageSpaceTooLowAlarm3410CBE2": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "LessThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "A node in your cluster is down to 20 GiB of free storage space.", + "MetricName": "FreeStorageSpace", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Minimum", + "Threshold": 2000 + } + }, + "IndexWritesBlockedTooHighAlarm5F7E9A55": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Your cluster is blocking write requests.", + "MetricName": "ClusterIndexWritesBlocked", + "Namespace": "AWS/ES", + "Period": 300, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "AutomatedSnapshotFailureTooHighAlarmA7918D4F": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "An automated snapshot failed. This failure is often the result of a red cluster health status.", + "MetricName": "AutomatedSnapshotFailure", + "Namespace": "AWS/ES", + "Period": 60, + "Statistic": "Maximum", + "Threshold": 1 + } + }, + "CPUUtilizationTooHighAlarmA395C469": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.", + "MetricName": "CPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "JVMMemoryPressureTooHighAlarm303EEA7C": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "JVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 80 + } + }, + "MasterCPUUtilizationTooHighAlarm1CE1084B": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 3, + "AlarmDescription": "Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.", + "MetricName": "MasterCPUUtilization", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + }, + "MasterJVMMemoryPressureTooHighAlarmBB15F770": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "AlarmDescription": "Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.", + "MetricName": "MasterJVMMemoryPressure", + "Namespace": "AWS/ES", + "Period": 900, + "Statistic": "Average", + "Threshold": 50 + } + } + }, + "Parameters": { + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3BucketBAF5BF3A": { + "Type": "String", + "Description": "S3 bucket for asset \"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\"" + }, + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbS3VersionKeyADB3CCA3": { + "Type": "String", + "Description": "S3 key for asset version \"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\"" + }, + "AssetParameters67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682dbArtifactHash322F5E2F": { + "Type": "String", + "Description": "Artifact hash for asset \"67a9971e29baab2bde3043bb70ce5b53318b95429a1ce9b189cf65223e8682db\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.ts new file mode 100644 index 000000000..77a79c83a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/integ.no-arguments.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 { LambdaToElasticSearchAndKibana } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-lambda-elasticsearch-kibana-stack'); + +const lambdaProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' +}; + +new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-kibana', { + lambdaFunctionProps: lambdaProps, + deployLambda: true, + domainName: 'test-domain1' +}); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts new file mode 100644 index 000000000..62ad18f0c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda-elasticsearch-kibana.test.ts @@ -0,0 +1,86 @@ +/** + * 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 { LambdaToElasticSearchAndKibana, LambdaToElasticSearchAndKibanaProps } from "../lib"; +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 = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }, + domainName: 'test-domain' + }; + + return new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-stack', props); +} + +test('snapshot test default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check domain names', () => { + const stack = new cdk.Stack(); + + deployNewFunc(stack); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolDomain', { + Domain: "test-domain", + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + } + }); + + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + DomainName: "test-domain", + }); +}); + +test('check getter methods', () => { + 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); +}); + +test('check exception for Missing existingObj from props for deploy = false', () => { + const stack = new cdk.Stack(); + + const props: LambdaToElasticSearchAndKibanaProps = { + deployLambda: true, + domainName: 'test-domain' + }; + + try { + new LambdaToElasticSearchAndKibana(stack, 'test-lambda-elasticsearch-stack', props); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js new file mode 100644 index 000000000..7a660ab80 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-elasticsearch-kibana/test/lambda/index.js @@ -0,0 +1,56 @@ +var AWS = require('aws-sdk'); +var path = require('path'); + +console.log('Loading function'); + +var esDomain = { + endpoint: process.env.DOMAIN_ENDPOINT, + region: process.env.AWS_REGION, + index: 'records', + doctype: 'movie' +}; + +var creds = new AWS.EnvironmentCredentials('AWS'); +var endpoint = new AWS.Endpoint(esDomain.endpoint); + +function postDocumentToES(doc, context) { + var req = new AWS.HttpRequest(endpoint); + + req.method = 'POST'; + req.path = path.join('/', esDomain.index, esDomain.doctype); + req.region = esDomain.region; + req.body = doc; + req.headers['presigned-expires'] = false; + req.headers['Host'] = esDomain.endpoint; + req.headers['Content-Type'] = 'application/json'; + + // Sign the request (Sigv4) + var signer = new AWS.Signers.V4(req, 'es'); + signer.addAuthorization(creds, new Date()); + + // Post document to ES + var send = new AWS.NodeHttpClient(); + send.handleRequest(req, null, function(httpResp) { + var body = ''; + httpResp.on('data', function (chunk) { + body += chunk; + }); + httpResp.on('end', function (chunk) { + console.log('All movie records added to ES.'); + context.succeed(); + }); + }, function(err) { + console.log('Error: ' + err); + context.fail(); + }); +} + +exports.handler = (event, context) => { + console.log('Received event:', JSON.stringify(event, null, 2)); + postDocumentToES("{ \"title\": \"Spirited Away\" }", context); + 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/aws-lambda-s3/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.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-lambda-s3/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-lambda-s3/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/.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-lambda-s3/README.md b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md new file mode 100644 index 000000000..219997e03 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/README.md @@ -0,0 +1,76 @@ +# aws-lambda-s3 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/aws-lambda-s3/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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'); + +new LambdaToS3(stack, 'LambdaToS3Pattern', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}); + +``` + +## Initializer + +``` text +new LambdaToS3(scope: Construct, id: string, props: LambdaToS3Props); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`LambdaToS3Props`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function.| +|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.| +|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| +|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`.| + + +|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.| + +## 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-konstruk/aws-lambda-s3/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b80d4b14ff6c70e28acf67909cdd5b91afc2f9b6 GIT binary patch literal 63914 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts new file mode 100644 index 000000000..a3154d45c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/lib/index.ts @@ -0,0 +1,163 @@ +/** + * 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 * as lambda from '@aws-cdk/aws-lambda'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as defaults from '@aws-solutions-konstruk/core'; +import { Construct } from '@aws-cdk/core'; + +/** + * @summary The properties for the LambdaToS3 class. + */ +export interface LambdaToS3Props { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide an existing function for the `existingLambdaObj` property. + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided properties to override the default properties for the Lambda function. + * If `deploy` is set to true only then this property is required. + * + * @default - Default properties are used. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any + /** + * 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 + /** + * Optional bucket permissions to grant to the Lambda function. + * One or more of the following may be specified: "Delete", "Put", "Read", "ReadWrite", "Write". + * + * @default - Read/write access is given to the Lambda function if no value is specified. + */ + readonly bucketPermissions?: string[] +} + +/** + * @summary The LambdaToS3 class. + */ +export class LambdaToS3 extends Construct { + // Private variables + private fn: lambda.Function; + private bucket: s3.Bucket; + + /** + * @summary Constructs a new instance of the LambdaToS3 class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {LambdaToS3Props} props - user provided props for the construct. + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: LambdaToS3Props) { + super(scope, id); + + // Setup the Lambda function + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Setup the S3 bucket + this.bucket = defaults.buildS3Bucket(this, { + deployBucket: props.deployBucket, + existingBucketObj: props.existingBucketObj, + bucketProps: props.bucketProps + }); + + // Configure environment variables + this.fn.addEnvironment('S3_BUCKET_NAME', this.bucket.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); + } + if (props.bucketPermissions.includes('Put')) { + this.bucket.grantPut(this.fn.grantPrincipal); + } + if (props.bucketPermissions.includes('Read')) { + this.bucket.grantRead(this.fn.grantPrincipal); + } + if (props.bucketPermissions.includes('ReadWrite')) { + this.bucket.grantReadWrite(this.fn.grantPrincipal); + } + if (props.bucketPermissions.includes('Write')) { + this.bucket.grantWrite(this.fn.grantPrincipal); + } + } else { + this.bucket.grantReadWrite(this.fn.grantPrincipal); + } + + // Add appropriate metadata + const s3BucketResource = this.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` + }] + } + }; + } + + /** + * @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-konstruk/aws-lambda-s3/package.json new file mode 100644 index 000000000..b920dad95 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/package.json @@ -0,0 +1,76 @@ +{ + "name": "@aws-solutions-konstruk/aws-lambda-s3", + "version": "0.8.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" + }, + "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 run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.konstruk.services.lambdas3", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "lambdas3" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.LambdaS3", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap new file mode 100644 index 000000000..95fb268b7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/__snapshots__/lambda-s3.test.js.snap @@ -0,0 +1,2178 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test deployment w/ s3 multiple permissions 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:DeleteObject*", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + Object { + "Action": Array [ + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test deployment w/ s3:Delete only 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:DeleteObject*", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test deployment w/ s3:Put only 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test deployment w/ s3:Read only 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test deployment w/ s3:ReadWrite only 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test deployment w/ s3:Write only 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test minimal deployment with new Lambda function 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; + +exports[`Test the bucketProps override 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": Object { + "Ref": "lambdatos3stackS3BucketB9FD9B29", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "lambdatos3stackS3BucketB9FD9B29", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "lambdatos3stackS3BucketB9FD9B29": 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": "lambdatos3stackS3LoggingBucketB82C3492", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + "WebsiteConfiguration": Object { + "IndexDocument": "index.main.html", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "lambdatos3stackS3LoggingBucketB82C3492": 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", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json new file mode 100644 index 000000000..644e9e3c0 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.expected.json @@ -0,0 +1,272 @@ +{ + "Description": "Integration Test for aws-lambda-s3", + "Resources": { + "testlambdas3S3LoggingBucketD42FC73D": { + "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" + } + ] + } + } + }, + "testlambdas3S3Bucket179A52E6": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testlambdas3S3LoggingBucketD42FC73D" + } + }, + "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" + } + ] + } + } + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdas3S3Bucket179A52E6", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testlambdas3S3Bucket179A52E6", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": { + "Ref": "testlambdas3S3Bucket179A52E6" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts new file mode 100644 index 000000000..21768b370 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.deployFunction.ts @@ -0,0 +1,37 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { LambdaToS3, LambdaToS3Props } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-lambda-s3'); +stack.templateOptions.description = 'Integration Test for aws-lambda-s3'; + +// Definitions +const props: LambdaToS3Props = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}; + +new LambdaToS3(stack, 'test-lambda-s3', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json new file mode 100644 index 000000000..6564857ba --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.expected.json @@ -0,0 +1,272 @@ +{ + "Description": "Integration Test for aws-lambda-s3", + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testlambdas3S3Bucket179A52E6", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testlambdas3S3Bucket179A52E6", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "S3_BUCKET_NAME": { + "Ref": "testlambdas3S3Bucket179A52E6" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "testlambdas3S3LoggingBucketD42FC73D": { + "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" + } + ] + } + } + }, + "testlambdas3S3Bucket179A52E6": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testlambdas3S3LoggingBucketD42FC73D" + } + }, + "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" + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts new file mode 100644 index 000000000..738079769 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/integ.existingFunction.ts @@ -0,0 +1,42 @@ +/** + * 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 { 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'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-lambda-s3'); +stack.templateOptions.description = 'Integration Test for aws-lambda-s3'; + +// Definitions +const lambdaFunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) +}; + +const func = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + +const props: LambdaToS3Props = { + deployLambda: false, + existingLambdaObj: func +}; + +new LambdaToS3(stack, 'test-lambda-s3', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts new file mode 100644 index 000000000..324d3506a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda-s3.test.ts @@ -0,0 +1,210 @@ +/** + * 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 lambda from "@aws-cdk/aws-lambda"; +import { LambdaToS3 } from '../lib'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test minimal deployment with new Lambda function +// -------------------------------------------------------------- +test('Test minimal deployment with new Lambda function', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3:Delete only +// -------------------------------------------------------------- +test('Test deployment w/ s3:Delete only', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Delete'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3:Put only +// -------------------------------------------------------------- +test('Test deployment w/ s3:Put only', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Put'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3:Read only +// -------------------------------------------------------------- +test('Test deployment w/ s3:Read only', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Read'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3:ReadWrite only +// -------------------------------------------------------------- +test('Test deployment w/ s3:ReadWrite only', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['ReadWrite'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3:Write only +// -------------------------------------------------------------- +test('Test deployment w/ s3:Write only', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Write'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ s3 multiple permissions +// -------------------------------------------------------------- +test('Test deployment w/ s3 multiple permissions', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Write', 'Delete'] + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test the getter methods +// -------------------------------------------------------------- +test('Test the getter methods', () => { + // Stack + const stack = new Stack(); + // Helper declaration + const pattern = new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketPermissions: ['Write'] + }); + // Assertion 1 + const func = pattern.lambdaFunction(); + expect(func).toBeDefined(); + // Assertion 2 + const bucket = pattern.s3Bucket(); + expect(bucket).toBeDefined(); +}); + +// -------------------------------------------------------------- +// Test the bucketProps override +// -------------------------------------------------------------- +test('Test the bucketProps override', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToS3(stack, 'lambda-to-s3-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + bucketProps: { + websiteIndexDocument: 'index.main.html' + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::S3::Bucket", { + WebsiteConfiguration: { + IndexDocument: 'index.main.html' + } + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js new file mode 100644 index 000000000..51fdc6953 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-s3/test/lambda/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +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/aws-lambda-sns/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.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-lambda-sns/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-lambda-sns/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/.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-lambda-sns/README.md b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md new file mode 100644 index 000000000..bd54833d6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/README.md @@ -0,0 +1,78 @@ +# aws-lambda-sns 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/aws-lambda-sns/| +|:-------------|:-------------| +

    + +| **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`| + +This AWS Solutions Konstruk 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'); + +new LambdaToSns(stack, 'LambdaToSnsPattern', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}); + +``` + +## Initializer + +``` text +new LambdaToSns(scope: Construct, id: string, props: LambdaToSnsProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`LambdaToSnsProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function.| +|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.| +|topicProps?|[`sns.TopicProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.TopicProps.html)|Optional user provided properties to override the default properties for the SNS topic.| +|enableEncryption?|`boolean`|Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in the encryptionKey property for this construct.| +|encryptionKey?|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.Key.html)|An optional, imported encryption key to encrypt the SNS topic with.| + +## 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.| +|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.| + +## 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-konstruk/aws-lambda-sns/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b6990c4ff3394bb11b23cec5e377bde24056344c GIT binary patch literal 67135 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 Z$%}!H-{E-r^N&$KI+})eP`B=f{4Y@On_B { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToSns(stack, 'lambda-to-sns-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`), + environment: { + LAMBDA_NAME: 'deployed-function' + } + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike("AWS::Lambda::Function", { + Environment: { + Variables: { + LAMBDA_NAME: 'deployed-function' + } + } + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: true + }); +}); + +// -------------------------------------------------------------- +// Test deployment with existing Lambda function +// -------------------------------------------------------------- +test('Test deployment with existing Lambda function', () => { + // Stack + const stack = new Stack(); + // Helper declaration + new LambdaToSns(stack, 'lambda-to-sns-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`), + environment: { + LAMBDA_NAME: 'override-function' + } + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike("AWS::Lambda::Function", { + Environment: { + Variables: { + LAMBDA_NAME: 'override-function' + } + } + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: true + }); +}); + +// -------------------------------------------------------------- +// Test deployment with imported encryption key +// -------------------------------------------------------------- +test('Test deployment with imported encryption key', () => { + // Stack + const stack = new Stack(); + // Setup + const kmsKey = new kms.Key(stack, 'imported-key', { + enableKeyRotation: false + }); + // Helper declaration + new LambdaToSns(stack, 'lambda-to-sns-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`), + environment: { + LAMBDA_NAME: 'deployed-function-no-enc' + } + }, + enableEncryption: true, + encryptionKey: kmsKey + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike("AWS::Lambda::Function", { + Environment: { + Variables: { + LAMBDA_NAME: 'deployed-function-no-enc' + } + } + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: false + }); +}); + +// -------------------------------------------------------------- +// Test the getter methods +// -------------------------------------------------------------- +test('Test the getter methods', () => { + // Stack + const stack = new Stack(); + // Helper declaration + const pattern = new LambdaToSns(stack, 'lambda-to-sns-stack', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } + }); + // Assertion 1 + const func = pattern.lambdaFunction(); + expect(func).toBeDefined(); + // Assertion 2 + const topic = pattern.snsTopic(); + expect(topic).toBeDefined(); +}); \ 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-konstruk/aws-lambda-sns/test/lambda/index.js new file mode 100644 index 000000000..51fdc6953 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-lambda-sns/test/lambda/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +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/aws-s3-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-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-s3-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-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/aws-s3-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-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-s3-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md new file mode 100644 index 000000000..188a26ea2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/README.md @@ -0,0 +1,85 @@ +# aws-s3-lambda 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/aws-s3-lambda/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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 stack = new Stack(app, 'test-s3-lambda-stack'); + +const props: S3ToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, +}; + +new S3ToLambda(stack, 'test-s3-lambda', props); + +``` + +## Initializer + +``` text +new S3ToLambda(scope: Construct, id: string, props: S3ToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`S3ToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function. If set to false, you must provide an existing function for the `existingLambdaObj` property.| +|existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|An optional, existing Lambda function. This property is required if `deployLambda` is set to false.| +|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.| +|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| +|s3EventSourceProps?|[`S3EventSourceProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda-event-sources.S3EventSourceProps.html)|Optional user provided props to override the default props for S3EventSourceProps| + +## 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 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| + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..5f46107d5de978378a6e85b7518fffe2b111a993 GIT binary patch literal 65047 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 { + // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies + const s3 = new (require('aws-sdk').S3)(); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const https = require(\\"https\\"); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const url = require(\\"url\\"); + log(JSON.stringify(event, undefined, 2)); + const props = event.ResourceProperties; + if (event.RequestType === 'Delete') { + props.NotificationConfiguration = {}; // this is how you clean out notifications + } + const req = { + Bucket: props.BucketName, + NotificationConfiguration: props.NotificationConfiguration + }; + return s3.putBucketNotificationConfiguration(req, (err, data) => { + log({ err, data }); + if (err) { + return submitResponse(\\"FAILED\\", err.message + \`\\\\nMore information in CloudWatch Log Stream: \${context.logStreamName}\`); + } + else { + return submitResponse(\\"SUCCESS\\"); + } + }); + function log(obj) { + console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj); + } + // tslint:disable-next-line:max-line-length + // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule + // to allow sending an error messge as a reason. + function submitResponse(responseStatus, reason) { + const responseBody = JSON.stringify({ + Status: responseStatus, + Reason: reason || \\"See the details in CloudWatch Log Stream: \\" + context.logStreamName, + PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + NoEcho: false, + }); + log({ responseBody }); + const parsedUrl = url.parse(event.ResponseURL); + const options = { + hostname: parsedUrl.hostname, + port: 443, + path: parsedUrl.path, + method: \\"PUT\\", + headers: { + \\"content-type\\": \\"\\", + \\"content-length\\": responseBody.length + } + }; + const request = https.request(options, (r) => { + log({ statusCode: r.statusCode, statusMessage: r.statusMessage }); + context.done(); + }); + request.on(\\"error\\", (error) => { + log({ sendError: error }); + context.done(); + }); + request.write(responseBody); + request.end(); + } +};", + }, + "Description": "AWS CloudFormation handler for \\"Custom::S3BucketNotifications\\" resources (@aws-cdk/aws-s3)", + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + "Timeout": 300, + }, + "Type": "AWS::Lambda::Function", + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "Bucket resource is '*' due to circular dependency with bucket and role creation at the same time", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "s3:PutBucketNotification", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", + "Roles": Array [ + Object { + "Ref": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "LambdaFunctionAllowBucketNotificationsFromS3Bucket25B4F189": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "s3.amazonaws.com", + "SourceAccount": Object { + "Ref": "AWS::AccountId", + }, + "SourceArn": Object { + "Fn::GetAtt": Array [ + "S3Bucket07682993", + "Arn", + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "S3Bucket07682993": 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": "S3LoggingBucket800A2B27", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "S3BucketNotifications58B5AD06": Object { + "DependsOn": Array [ + "LambdaFunctionAllowBucketNotificationsFromS3Bucket25B4F189", + ], + "Properties": Object { + "BucketName": Object { + "Ref": "S3Bucket07682993", + }, + "NotificationConfiguration": Object { + "LambdaFunctionConfigurations": Array [ + Object { + "Events": Array [ + "s3:ObjectCreated:*", + ], + "LambdaFunctionArn": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + }, + ], + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691", + "Arn", + ], + }, + }, + "Type": "Custom::S3BucketNotifications", + }, + "S3LoggingBucket800A2B27": 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", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json new file mode 100644 index 000000000..f5a11688e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.expected.json @@ -0,0 +1,366 @@ +{ + "Resources": { + "S3LoggingBucket800A2B27": { + "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" + } + ] + } + } + }, + "S3Bucket07682993": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "S3LoggingBucket800A2B27" + } + }, + "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" + } + ] + } + } + }, + "S3BucketNotifications58B5AD06": { + "Type": "Custom::S3BucketNotifications", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691", + "Arn" + ] + }, + "BucketName": { + "Ref": "S3Bucket07682993" + }, + "NotificationConfiguration": { + "LambdaFunctionConfigurations": [ + { + "Events": [ + "s3:ObjectCreated:*" + ], + "LambdaFunctionArn": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + ] + } + }, + "DependsOn": [ + "LambdaFunctionAllowBucketNotificationsFromtests3lambdastackS3BucketAA5BB5A99E8B1157" + ] + }, + "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": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "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." + } + ] + } + } + }, + "LambdaFunctionAllowBucketNotificationsFromtests3lambdastackS3BucketAA5BB5A99E8B1157": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "s3.amazonaws.com", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "SourceArn": { + "Fn::GetAtt": [ + "S3Bucket07682993", + "Arn" + ] + } + } + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:PutBucketNotification", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", + "Roles": [ + { + "Ref": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Bucket resource is '*' due to circular dependency with bucket and role creation at the same time" + } + ] + } + } + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require(\"https\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require(\"url\");\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse(\"FAILED\", err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse(\"SUCCESS\");\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || \"See the details in CloudWatch Log Stream: \" + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: \"PUT\",\n headers: {\n \"content-type\": \"\",\n \"content-length\": responseBody.length\n }\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on(\"error\", (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Timeout": 300 + }, + "DependsOn": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda function has permission to write CloudWatch Logs via AWSLambdaBasicExecutionRole policy attached to the lambda role" + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts new file mode 100644 index 000000000..fb126fa64 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.existing-s3-bucket.ts @@ -0,0 +1,51 @@ +/** + * 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 { 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'; +const app = new App(); + +// Empty arguments +const stack = new Stack(app, 'test-s3-lambda-stack'); + +const myBucket: s3.Bucket = defaults.buildS3Bucket(stack, {}); + +// Extract the CfnBucket from the s3Bucket +const s3BucketResource = myBucket.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` + }] + } +}; + +const props: S3ToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + deployBucket: false, + existingBucketObj: myBucket +}; + +new S3ToLambda(stack, 'test-s3-lambda', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..4d1829de0 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.expected.json @@ -0,0 +1,366 @@ +{ + "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": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "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." + } + ] + } + } + }, + "LambdaFunctionAllowBucketNotificationsFromtests3lambdastackS3BucketAA5BB5A99E8B1157": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "s3.amazonaws.com", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "SourceArn": { + "Fn::GetAtt": [ + "S3Bucket07682993", + "Arn" + ] + } + } + }, + "S3LoggingBucket800A2B27": { + "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" + } + ] + } + } + }, + "S3Bucket07682993": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "S3LoggingBucket800A2B27" + } + }, + "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" + } + ] + } + } + }, + "S3BucketNotifications58B5AD06": { + "Type": "Custom::S3BucketNotifications", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691", + "Arn" + ] + }, + "BucketName": { + "Ref": "S3Bucket07682993" + }, + "NotificationConfiguration": { + "LambdaFunctionConfigurations": [ + { + "Events": [ + "s3:ObjectCreated:*" + ], + "LambdaFunctionArn": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + ] + } + }, + "DependsOn": [ + "LambdaFunctionAllowBucketNotificationsFromtests3lambdastackS3BucketAA5BB5A99E8B1157" + ] + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:PutBucketNotification", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", + "Roles": [ + { + "Ref": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Bucket resource is '*' due to circular dependency with bucket and role creation at the same time" + } + ] + } + } + }, + "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Description": "AWS CloudFormation handler for \"Custom::S3BucketNotifications\" resources (@aws-cdk/aws-s3)", + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, import/no-extraneous-dependencies\n const s3 = new (require('aws-sdk').S3)();\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const https = require(\"https\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const url = require(\"url\");\n log(JSON.stringify(event, undefined, 2));\n const props = event.ResourceProperties;\n if (event.RequestType === 'Delete') {\n props.NotificationConfiguration = {}; // this is how you clean out notifications\n }\n const req = {\n Bucket: props.BucketName,\n NotificationConfiguration: props.NotificationConfiguration\n };\n return s3.putBucketNotificationConfiguration(req, (err, data) => {\n log({ err, data });\n if (err) {\n return submitResponse(\"FAILED\", err.message + `\\nMore information in CloudWatch Log Stream: ${context.logStreamName}`);\n }\n else {\n return submitResponse(\"SUCCESS\");\n }\n });\n function log(obj) {\n console.error(event.RequestId, event.StackId, event.LogicalResourceId, obj);\n }\n // tslint:disable-next-line:max-line-length\n // adapted from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule\n // to allow sending an error messge as a reason.\n function submitResponse(responseStatus, reason) {\n const responseBody = JSON.stringify({\n Status: responseStatus,\n Reason: reason || \"See the details in CloudWatch Log Stream: \" + context.logStreamName,\n PhysicalResourceId: event.PhysicalResourceId || event.LogicalResourceId,\n StackId: event.StackId,\n RequestId: event.RequestId,\n LogicalResourceId: event.LogicalResourceId,\n NoEcho: false,\n });\n log({ responseBody });\n const parsedUrl = url.parse(event.ResponseURL);\n const options = {\n hostname: parsedUrl.hostname,\n port: 443,\n path: parsedUrl.path,\n method: \"PUT\",\n headers: {\n \"content-type\": \"\",\n \"content-length\": responseBody.length\n }\n };\n const request = https.request(options, (r) => {\n log({ statusCode: r.statusCode, statusMessage: r.statusMessage });\n context.done();\n });\n request.on(\"error\", (error) => {\n log({ sendError: error });\n context.done();\n });\n request.write(responseBody);\n request.end();\n }\n};" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Timeout": 300 + }, + "DependsOn": [ + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleDefaultPolicy2CF63D36", + "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W58", + "reason": "Lambda function has permission to write CloudWatch Logs via AWSLambdaBasicExecutionRole policy attached to the lambda role" + } + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts new file mode 100644 index 000000000..b32792b34 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/integ.no-arguments.ts @@ -0,0 +1,33 @@ +/** + * 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 { S3ToLambda, S3ToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +const app = new App(); + +// Empty arguments +const stack = new Stack(app, 'test-s3-lambda-stack'); + +const props: S3ToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, +}; + +new S3ToLambda(stack, 'test-s3-lambda', props); +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js new file mode 100644 index 000000000..743e4fdbb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/lambda/index.js @@ -0,0 +1,8 @@ +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `Hello, CDK! You've hit ${event.path}\n` + }; + }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts new file mode 100644 index 000000000..3533808a8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-s3-lambda/test/s3-lambda.test.ts @@ -0,0 +1,47 @@ +/** + * 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 { 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'; + +function deployNewFunc(stack: cdk.Stack) { + const props: S3ToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + }; + + return new S3ToLambda(stack, 'test-s3-lambda', props); +} + +test('snapshot test S3ToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: S3ToLambda = deployNewFunc(stack); + + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.s3Bucket()).toBeInstanceOf(s3.Bucket); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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-konstruk/aws-sns-lambda/.gitignore b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.gitignore new file mode 100644 index 000000000..8626f2274 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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/aws-sns-lambda/.npmignore b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-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-sns-lambda/README.md b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md new file mode 100644 index 000000000..5fc948a44 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/README.md @@ -0,0 +1,83 @@ +# aws-sns-lambda 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/aws-sns-lambda/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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 stack = new Stack(app, 'test-sns-lambda'); + +// Definitions +const props: SnsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}; + +new SnsToLambda(stack, 'test-sns-lambda', props); + +``` + +## Initializer + +``` text +new SnsToLambda(scope: Construct, id: string, props: SnsToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`S3ToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function. If set to false, you must provide an existing function for the `existingLambdaObj` property.| +|existingLambdaObj?|[`lambda.Function`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html)|An optional, existing Lambda function. This property is required if `deployLambda` is set to false.| +|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| +|topicProps?|[`sns.TopicProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-sns.TopicProps.html)|Optional user provided properties to override the default properties for the SNS topic.| +|enableEncryption?|`boolean`|Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in the encryptionKey property for this construct.| +|encryptionKey?|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.Key.html)|An optional, imported encryption key to encrypt the SNS topic with.| + +## 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.| +|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.| + +## 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-konstruk/aws-sns-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..8a207f511a492bee068787d3f13f8553cc2a060c GIT binary patch literal 65851 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts new file mode 100644 index 000000000..970706eec --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/lib/index.ts @@ -0,0 +1,122 @@ +/** + * 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 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 { Construct } from '@aws-cdk/core'; +import { SnsEventSource } from '@aws-cdk/aws-lambda-event-sources'; + +/** + * @summary The properties for the SnsToLambda class. + */ +export interface SnsToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide an existing function for the `existingLambdaObj` property. + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided properties to override the default properties for the Lambda function. + * If `deploy` is set to true only then this property is required. + * + * @default - Default properties are used. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any + /** + * Optional user provided properties to override the default properties for the SNS topic. + * + * @default - Default properties are used. + */ + readonly topicProps?: sns.TopicProps | any + /** + * Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in + * the encryptionKey property for this construct. + * + * @default - true (encryption enabled, managed by this CDK app). + */ + readonly enableEncryption?: boolean + /** + * An optional, imported encryption key to encrypt the SNS topic with. + * + * @default - not specified. + */ + readonly encryptionKey?: kms.Key +} + +/** + * @summary The SnsToLambda class. + */ +export class SnsToLambda extends Construct { + // Private variables + private fn: lambda.Function; + private topic: sns.Topic; + + /** + * @summary Constructs a new instance of the LambdaToSns class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {LambdaToSnsProps} props - user provided props for the construct. + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: SnsToLambdaProps) { + super(scope, id); + + // Setup the Lambda function + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Setup the SNS topic + this.topic = 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; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json new file mode 100644 index 000000000..15812c402 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/package.json @@ -0,0 +1,83 @@ +{ + "name": "@aws-solutions-konstruk/aws-sns-lambda", + "version": "0.8.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" + }, + "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.snslambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "snslambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.SnsLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-sns": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-sns": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-kms": "~1.25.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap new file mode 100644 index 000000000..bb96c684a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/__snapshots__/sns-lambda.test.js.snap @@ -0,0 +1,233 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test SnsToLambda default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": Object { + "Description": "Artifact hash for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": Object { + "Description": "S3 bucket for asset \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": Object { + "Description": "S3 key for asset version \\"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionAllowInvoketestsnslambdaSnsTopicEB0543A084BF6FA6": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "sns.amazonaws.com", + "SourceArn": Object { + "Ref": "testsnslambdaSnsTopic52CA159E", + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionSnsTopic70134CE0": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Protocol": "lambda", + "TopicArn": Object { + "Ref": "testsnslambdaSnsTopic52CA159E", + }, + }, + "Type": "AWS::SNS::Subscription", + }, + "testsnslambdaEncryptionKeyDDDF040B": 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", + }, + "testsnslambdaSnsTopic52CA159E": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "testsnslambdaEncryptionKeyDDDF040B", + }, + }, + "Type": "AWS::SNS::Topic", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json new file mode 100644 index 000000000..0dd935987 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.expected.json @@ -0,0 +1,230 @@ +{ + "Description": "Integration Test for aws-sns-lambda", + "Resources": { + "testsnslambdaEncryptionKeyDDDF040B": { + "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": "*" + } + ], + "Version": "2012-10-17" + }, + "EnableKeyRotation": true + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testsnslambdaSnsTopic52CA159E": { + "Type": "AWS::SNS::Topic", + "Properties": { + "KmsMasterKeyId": { + "Ref": "testsnslambdaEncryptionKeyDDDF040B" + } + } + }, + "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": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7" + } + ] + } + ] + } + ] + ] + } + }, + "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." + } + ] + } + } + }, + "LambdaFunctionAllowInvoketestsnslambdaSnsTopic02988CE1827B8D82": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "testsnslambdaSnsTopic52CA159E" + } + } + }, + "LambdaFunctionSnsTopic70134CE0": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "testsnslambdaSnsTopic52CA159E" + }, + "Endpoint": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + } + } + } + }, + "Parameters": { + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3Bucket9E1964CB": { + "Type": "String", + "Description": "S3 bucket for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8S3VersionKey7153CEE7": { + "Type": "String", + "Description": "S3 key for asset version \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + }, + "AssetParameters0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8ArtifactHash8D9AD644": { + "Type": "String", + "Description": "Artifact hash for asset \"0c3255e93ffe7a906c7422e9f0e9cc4c7fd86ee996ee3bb302e2f134b38463c8\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts new file mode 100644 index 000000000..4b446cbd0 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/integ.no-arguments.ts @@ -0,0 +1,37 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { SnsToLambda, SnsToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sns-lambda'); +stack.templateOptions.description = 'Integration Test for aws-sns-lambda'; + +// Definitions +const props: SnsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}; + +new SnsToLambda(stack, 'test-sns-lambda', props); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js new file mode 100644 index 000000000..743e4fdbb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/lambda/index.js @@ -0,0 +1,8 @@ +exports.handler = async function(event) { + console.log('request:', JSON.stringify(event, undefined, 2)); + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `Hello, CDK! You've hit ${event.path}\n` + }; + }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts new file mode 100644 index 000000000..3db6f010e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sns-lambda/test/sns-lambda.test.ts @@ -0,0 +1,47 @@ +/** + * 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 { 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 = { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + }; + + return new SnsToLambda(stack, 'test-sns-lambda', props); +} + +test('snapshot test SnsToLambda default params', () => { + const stack = new cdk.Stack(); + deployNewFunc(stack); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check getter methods', () => { + const stack = new cdk.Stack(); + + const construct: SnsToLambda = deployNewFunc(stack); + + expect(construct.lambdaFunction()).toBeInstanceOf(lambda.Function); + expect(construct.snsTopic()).toBeInstanceOf(Topic); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.eslintignore b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.eslintignore new file mode 100644 index 000000000..0819e2e65 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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-konstruk/aws-sqs-lambda/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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-konstruk/aws-sqs-lambda/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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-konstruk/aws-sqs-lambda/README.md new file mode 100644 index 000000000..250527ad9 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/README.md @@ -0,0 +1,81 @@ +# aws-sqs-lambda 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/aws-sqs-lambda/| +|:-------------|:-------------| +
    + +| **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`| + +This AWS Solutions Konstruk 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'); + +new SqsToLambda(stack, 'SqsToLambdaPattern', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + deployDeadLetterQueue: true, + maxReceiveCount: 15 +}); + +``` + +## Initializer + +``` text +new SqsToLambda(scope: Construct, id: string, props: SqsToLambdaProps); +``` + +_Parameters_ + +* scope [`Construct`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html) +* id `string` +* props [`SqsToLambdaProps`](#pattern-construct-props) + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function.| +|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.| +|maxReceiveCount|`number`|The number of times a message can be unsuccesfully dequeued before being moved to the dead letter queue.| + +## 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.| + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/architecture.png b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..917d5e875134fe90d9b17acc3a48ca050264dfde GIT binary patch literal 97539 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 ?gDWw9%Eupd&w=6tH$ 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 literal 0 HcmV?d00001 diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts new file mode 100644 index 000000000..5dbac146a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/lib/index.ts @@ -0,0 +1,146 @@ +/** + * 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 * 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 { Construct } from '@aws-cdk/core'; +import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources'; + +/** + * @summary The properties for the SqsToLambda class. + */ +export interface SqsToLambdaProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide an existing function for the `existingLambdaObj` property. + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided properties to override the default properties for the Lambda function. + * If `deploy` is set to true only then this property is required. + * + * @default - Default properties are used. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any + /** + * Optional user provided properties + * + * @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. + * + * @default - required field. + */ + readonly deployDeadLetterQueue: boolean, + /** + * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * + * @default - required field. + */ + readonly maxReceiveCount: number +} + +/** + * @summary The SqsToLambda class. + */ +export class SqsToLambda extends Construct { + // Private variables + private queue: sqs.Queue; + private fn: lambda.Function; + private encryptionKey: kms.Key; + + /** + * @summary Constructs a new instance of the SqsToLambda class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {CloudFrontToApiGatewayToLambdaProps} props - user provided props for the construct. + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: SqsToLambdaProps) { + super(scope, id); + + // Setup the encryption key + this.encryptionKey = defaults.buildEncryptionKey(scope, props.encryptionKeyProps); + + // Setup the Lambda function + this.fn = defaults.buildLambdaFunction(scope, { + deployLambda: props.deployLambda, + existingLambdaObj: props.existingLambdaObj, + lambdaFunctionProps: props.lambdaFunctionProps + }); + + // Setup the dead letter queue, if applicable + let dlqi: sqs.DeadLetterQueue | undefined; + if (props.deployDeadLetterQueue) { + const dlq: sqs.Queue = defaults.buildQueue(scope, 'deadLetterQueue', { + encryptionKey: this.encryptionKey, + queueProps: props.queueProps + }); + dlqi = defaults.buildDeadLetterQueue({ + deadLetterQueue: dlq, + maxReceiveCount: props.maxReceiveCount + }); + } + + // Setup the queue + this.queue = defaults.buildQueue(scope, 'queue', { + encryptionKey: this.encryptionKey, + 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; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json new file mode 100644 index 000000000..78cffbe9b --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/package.json @@ -0,0 +1,80 @@ +{ + "name": "@aws-solutions-konstruk/aws-sqs-lambda", + "version": "0.8.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" + }, + "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 run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.konstruk.services.sqslambda", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "sqslambda" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.SqsLambda", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap new file mode 100644 index 000000000..02aae6d9a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/__snapshots__/test.sqs-lambda.test.js.snap @@ -0,0 +1,534 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pattern deployment w/ Existing Lambda Function 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "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": "*", + }, + 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", + }, + "ExistingLambdaFunctionF606C520": Object { + "DependsOn": Array [ + "ExistingLambdaFunctionServiceRoleDefaultPolicy2431D213", + "ExistingLambdaFunctionServiceRole7CC6DE65", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "ExistingLambdaFunctionServiceRole7CC6DE65", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "ExistingLambdaFunctionServiceRole7CC6DE65": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "ExistingLambdaFunctionServiceRoleDefaultPolicy2431D213": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "ExistingLambdaFunctionServiceRoleDefaultPolicy2431D213", + "Roles": Array [ + Object { + "Ref": "ExistingLambdaFunctionServiceRole7CC6DE65", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "ExistingLambdaFunctionSqsEventSourcequeue85681C04": Object { + "Properties": Object { + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + "FunctionName": Object { + "Ref": "ExistingLambdaFunctionF606C520", + }, + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "queue276F7297": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; + +exports[`Pattern deployment w/ new Lambda function and default props 1`] = ` +Object { + "Parameters": Object { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": Object { + "Description": "Artifact hash for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": Object { + "Description": "S3 bucket for asset \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": Object { + "Description": "S3 key for asset version \\"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\\"", + "Type": "String", + }, + }, + "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": "*", + }, + Object { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + }, + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::KMS::Key", + "UpdateReplacePolicy": "Retain", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + }, + Object { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "LambdaFunctionSqsEventSourcequeue7A15E0AF": Object { + "Properties": Object { + "EventSourceArn": Object { + "Fn::GetAtt": Array [ + "queue276F7297", + "Arn", + ], + }, + "FunctionName": Object { + "Ref": "LambdaFunctionBF21E41F", + }, + }, + "Type": "AWS::Lambda::EventSourceMapping", + }, + "deadLetterQueue3F848E28": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + }, + "Type": "AWS::SQS::Queue", + }, + "queue276F7297": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Fn::GetAtt": Array [ + "EncryptionKey1B843E66", + "Arn", + ], + }, + "RedrivePolicy": Object { + "deadLetterTargetArn": Object { + "Fn::GetAtt": Array [ + "deadLetterQueue3F848E28", + "Arn", + ], + }, + "maxReceiveCount": 15, + }, + }, + "Type": "AWS::SQS::Queue", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json new file mode 100644 index 000000000..de6b099dc --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.expected.json @@ -0,0 +1,292 @@ +{ + "Description": "Integration Test for aws-sqs-lambda", + "Resources": { + "EncryptionKey1B843E66": { + "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" + }, + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "LambdaFunctionSqsEventSourcetestsqslambdaqueue583E2E6C926C265C": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + }, + "FunctionName": { + "Ref": "LambdaFunctionBF21E41F" + } + } + }, + "deadLetterQueue3F848E28": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + }, + "queue276F7297": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "deadLetterQueue3F848E28", + "Arn" + ] + }, + "maxReceiveCount": 3 + } + } + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts new file mode 100644 index 000000000..6858f97c5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.deployFunction.ts @@ -0,0 +1,41 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { SqsToLambda, SqsToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sqs-lambda'); +stack.templateOptions.description = 'Integration Test for aws-sqs-lambda'; + +// Definitions +const props: SqsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + queueProps: {}, + deployDeadLetterQueue: true, + maxReceiveCount: 3, + encryptionKeyProps: {} +}; + +new SqsToLambda(stack, 'test-sqs-lambda', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json new file mode 100644 index 000000000..2531f650d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.expected.json @@ -0,0 +1,292 @@ +{ + "Description": "Integration Test for aws-sqs-lambda", + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + } + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1" + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "LambdaFunctionSqsEventSourcetestsqslambdaqueue583E2E6C926C265C": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": { + "Fn::GetAtt": [ + "queue276F7297", + "Arn" + ] + }, + "FunctionName": { + "Ref": "LambdaFunctionBF21E41F" + } + } + }, + "EncryptionKey1B843E66": { + "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" + }, + "deadLetterQueue3F848E28": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + } + } + }, + "queue276F7297": { + "Type": "AWS::SQS::Queue", + "Properties": { + "KmsMasterKeyId": { + "Fn::GetAtt": [ + "EncryptionKey1B843E66", + "Arn" + ] + }, + "RedrivePolicy": { + "deadLetterTargetArn": { + "Fn::GetAtt": [ + "deadLetterQueue3F848E28", + "Arn" + ] + }, + "maxReceiveCount": 3 + } + } + } + }, + "Parameters": { + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3Bucket749AC458": { + "Type": "String", + "Description": "S3 bucket for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420S3VersionKeyFF5CC16D": { + "Type": "String", + "Description": "S3 key for asset version \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + }, + "AssetParameters8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420ArtifactHashA71E92AD": { + "Type": "String", + "Description": "Artifact hash for asset \"8efd3dd9643a4d64a128ad582cab718a1e464bcc719bbbcf0e7b0481188a0420\"" + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts new file mode 100644 index 000000000..63337bc51 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/integ.existingFunction.ts @@ -0,0 +1,46 @@ +/** + * 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 { 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'; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-sqs-lambda'); +stack.templateOptions.description = 'Integration Test for aws-sqs-lambda'; + +// Definitions +const lambdaFunctionProps = { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) +}; + +const func = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + +const props: SqsToLambdaProps = { + deployLambda: false, + existingLambdaObj: func, + queueProps: {}, + deployDeadLetterQueue: true, + maxReceiveCount: 3, + encryptionKeyProps: {} +}; + +new SqsToLambda(stack, 'test-sqs-lambda', props); + +// Synth +app.synth(); diff --git a/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js new file mode 100644 index 000000000..51fdc6953 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/lambda/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2020 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. + */ + +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/aws-sqs-lambda/test/test.sqs-lambda.test.ts b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts new file mode 100644 index 000000000..a83f2945e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/aws-sqs-lambda/test/test.sqs-lambda.test.ts @@ -0,0 +1,163 @@ +/** + * 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 { SqsToLambda, SqsToLambdaProps } from "../lib"; +import * as lambda from '@aws-cdk/aws-lambda'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Pattern deployment w/ new Lambda function and +// default properties +// -------------------------------------------------------------- +test('Pattern deployment w/ new Lambda function and default props', () => { + // Initial Setup + const stack = new Stack(); + const props: SqsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }, + deployDeadLetterQueue: true, + maxReceiveCount: 15, + queueProps: {}, + encryptionKeyProps: {}, + }; + new SqsToLambda(stack, 'test-sqs-lambda', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Pattern deployment w/ new Lambda function and +// overridden properties +// -------------------------------------------------------------- +test('Pattern deployment w/ new Lambda function and overridden props', () => { + // Initial Setup + const stack = new Stack(); + const props: SqsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`), + environment: { + OVERRIDE: "TRUE" + } + }, + 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'); + // Assertion 2 + expect(app.sqsQueue()).toHaveProperty('fifo', true); +}); + +// -------------------------------------------------------------- +// Pattern Deployment w/ Existing Lambda function +// -------------------------------------------------------------- +test('Pattern deployment w/ Existing Lambda Function', () => { + // Initial Setup + const stack = new Stack(); + const fn = new lambda.Function(stack, 'ExistingLambdaFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }); + const props: SqsToLambdaProps = { + deployLambda: false, + existingLambdaObj: fn, + deployDeadLetterQueue: false, + encryptionKeyProps: {}, + maxReceiveCount: 0, + queueProps: {} + }; + new SqsToLambda(stack, 'test-apigateway-lambda', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test the getter methods +// -------------------------------------------------------------- +test('Test getter methods', () => { + // Initial Setup + const stack = new Stack(); + const props: SqsToLambdaProps = { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + 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(); + // Assertion 2 + expect(app.sqsQueue()).toBeDefined(); +}); + +// -------------------------------------------------------------- +// Test error handling for existing Lambda function +// -------------------------------------------------------------- +test('Test error handling for existing Lambda function', () => { + // Initial Setup + const stack = new Stack(); + const props: SqsToLambdaProps = { + deployLambda: false, + existingLambdaObj: undefined, + deployDeadLetterQueue: false, + encryptionKeyProps: {}, + maxReceiveCount: 0, + queueProps: {} + }; + // Assertion 1 + expect(() => { + new SqsToLambda(stack, 'test-sqs-lambda', props); + }).toThrowError(); +}); + +// -------------------------------------------------------------- +// Test error handling for new Lambda function +// w/o required properties +// -------------------------------------------------------------- +test('Test error handling for new Lambda function w/o required properties', () => { + // Initial Setup + const stack = new Stack(); + const props: SqsToLambdaProps = { + deployLambda: true, + deployDeadLetterQueue: false, + encryptionKeyProps: {}, + maxReceiveCount: 0, + queueProps: {} + }; + // Assertion 1 + expect(() => { + new SqsToLambda(stack, 'test-sqs-lambda', props); + }).toThrowError(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/.eslintignore b/source/patterns/@aws-solutions-konstruk/core/.eslintignore new file mode 100644 index 000000000..23c247780 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/.eslintignore @@ -0,0 +1,7 @@ +index.js +lib/*.js +test/*.js +*.d.ts +coverage +test/lambda/index.js +test/lambda-test/index.js \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/.gitignore b/source/patterns/@aws-solutions-konstruk/core/.gitignore new file mode 100644 index 000000000..f4b17fa0b --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/.gitignore @@ -0,0 +1,17 @@ +index.js +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-konstruk/core/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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-konstruk/core/README.md b/source/patterns/@aws-solutions-konstruk/core/README.md new file mode 100644 index 000000000..9ecafd285 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/README.md @@ -0,0 +1,69 @@ +# 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/index.ts b/source/patterns/@aws-solutions-konstruk/core/index.ts new file mode 100644 index 000000000..a8de6950e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/index.ts @@ -0,0 +1,42 @@ +/** + * 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. + */ + +export * from './lib/apigateway-defaults'; +export * from './lib/apigateway-helper'; +export * from './lib/dynamodb-table-defaults'; +export * from './lib/iot-topic-rule-defaults'; +export * from './lib/kinesis-analytics-defaults'; +export * from './lib/kinesis-analytics-helper'; +export * from './lib/kinesis-streams-defaults'; +export * from './lib/kinesis-streams-helper'; +export * from './lib/kinesis-firehose-s3-defaults'; +export * from './lib/lambda-event-source-mapping-defaults'; +export * from './lib/kms-defaults'; +export * from './lib/kms-helper'; +export * from './lib/lambda-defaults'; +export * from './lib/lambda-helper'; +export * from './lib/s3-bucket-defaults'; +export * from './lib/s3-bucket-helper'; +export * from './lib/sns-defaults'; +export * from './lib/sns-helper'; +export * from './lib/sqs-defaults'; +export * from './lib/sqs-helper'; +export * from './lib/cloudwatch-log-group-defaults'; +export * from './lib/cloudfront-distribution-helper'; +export * from './lib/cloudfront-distribution-defaults'; +export * from './lib/utils'; +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 diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts new file mode 100644 index 000000000..03340c9eb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-defaults.ts @@ -0,0 +1,69 @@ +/** + * 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 api from '@aws-cdk/aws-apigateway'; +import * as lambda from '@aws-cdk/aws-lambda'; + +export function DefaultGlobalLambdaRestApiProps(_existingLambdaObj: lambda.Function) { + const defaultGatewayProps: api.LambdaRestApiProps = { + handler: _existingLambdaObj, + options: { + endpointTypes: [api.EndpointType.EDGE], + cloudWatchRole: false, + // Configure API Gateway Execution logging + deployOptions: { + loggingLevel: api.MethodLoggingLevel.INFO, + dataTraceEnabled: true + }, + defaultMethodOptions: { + authorizationType: api.AuthorizationType.IAM + } + } + }; + return defaultGatewayProps; +} + +export function DefaultRegionalLambdaRestApiProps(_existingLambdaObj: lambda.Function) { + const defaultGatewayProps: api.LambdaRestApiProps = { + handler: _existingLambdaObj, + options: { + endpointTypes: [api.EndpointType.REGIONAL], + cloudWatchRole: false, + // Configure API Gateway Execution logging + deployOptions: { + loggingLevel: api.MethodLoggingLevel.INFO, + dataTraceEnabled: true + }, + defaultMethodOptions: { + authorizationType: api.AuthorizationType.IAM + } + } + }; + return defaultGatewayProps; +} + +export function DefaultGlobalApiProps() { + const defaultGatewayProps: api.RestApiProps = { + endpointTypes: [api.EndpointType.EDGE], + cloudWatchRole: false, + // Configure API Gateway Execution logging + deployOptions: { + loggingLevel: api.MethodLoggingLevel.INFO, + dataTraceEnabled: true + }, + defaultMethodOptions: { + authorizationType: api.AuthorizationType.IAM + } + }; + return defaultGatewayProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts new file mode 100644 index 000000000..bb5fc0c1c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/apigateway-helper.ts @@ -0,0 +1,175 @@ +/** + * 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 * as logs from '@aws-cdk/aws-logs'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as api from '@aws-cdk/aws-apigateway'; +import * as iam from '@aws-cdk/aws-iam'; +import * as apiDefaults from './apigateway-defaults'; +import { DefaultLogGroupProps } from './cloudwatch-log-group-defaults'; +import { overrideProps } from './utils'; + +/** + * Creates and configures an api.LambdaRestApi. + * @param scope - the construct to which the LambdaRestApi should be attached to. + * @param defaultApiGatewayProps - the default properties for the LambdaRestApi. + * @param apiGatewayProps - (optional) user-specified properties to override the default properties. + */ +function configureLambdaRestApi(scope: cdk.Construct, defaultApiGatewayProps: api.LambdaRestApiProps, + apiGatewayProps?: api.LambdaRestApiProps): api.RestApi { + // Define the API object + let _api: api.RestApi; + if (apiGatewayProps) { + // If property overrides have been provided, incorporate them and deploy + const _apiGatewayProps = overrideProps(defaultApiGatewayProps, apiGatewayProps); + _api = new api.LambdaRestApi(scope, 'RestApi', _apiGatewayProps); + } else { + // If no property overrides, deploy using the default configuration + _api = new api.LambdaRestApi(scope, 'RestApi', defaultApiGatewayProps); + } + // Configure API access logging + configureApiAccessLogging(scope, _api); + + // Configure Usage Plan + _api.addUsagePlan('UsagePlan', { + apiStages: [{ + api: _api, + stage: _api.deploymentStage + }] + }); + + // Return the API object + return _api; +} + +/** + * Create and configures access logging for API Gateway resources. + * @param scope - the construct to which the access logging capabilities should be attached to. + * @param _api - an existing api.RestApi or api.LambdaRestApi. + */ +function configureApiAccessLogging(scope: cdk.Construct, _api: api.RestApi): void { + // Configure log group for API Gateway AccessLogging + const logGroup = new logs.LogGroup(scope, 'ApiAccessLogGroup', DefaultLogGroupProps()); + // Configure the API stage + const stage: api.CfnStage = _api.deploymentStage.node.findChild('Resource') as api.CfnStage; + stage.accessLogSetting = { + destinationArn: logGroup.logGroupArn, + format: "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }; + // Configure the API deployment + const deployment: api.CfnDeployment = _api.latestDeployment?.node.findChild('Resource') as api.CfnDeployment; + deployment.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W45', + reason: `ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource` + }] + } + }; + // Setup the IAM Role for API Gateway CloudWatch access + const restApiCloudwatchRole = new iam.Role(scope, 'LambdaRestApiCloudWatchRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + inlinePolicies: { + LambdaRestApiCloudWatchRolePolicy: new iam.PolicyDocument({ + statements: [new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:DescribeLogGroups', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + 'logs:GetLogEvents', + 'logs:FilterLogEvents' + ], + resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:*`] + }) + ] + }) + } + }); + // Create and configure AWS::ApiGateway::Account with CloudWatch Role for ApiGateway + const CfnApi = _api.node.findChild('Resource') as api.CfnRestApi; + const cfnAccount: api.CfnAccount = new api.CfnAccount(scope, 'LambdaRestApiAccount', { + cloudWatchRoleArn: restApiCloudwatchRole.roleArn + }); + cfnAccount.addDependsOn(CfnApi); +} + +/** + * Creates and configures an api.RestApi. + * @param scope - the construct to which the RestApi should be attached to. + * @param defaultApiGatewayProps - the default properties for the RestApi. + * @param apiGatewayProps - (optional) user-specified properties to override the default properties. + */ +function configureRestApi(scope: cdk.Construct, defaultApiGatewayProps: api.RestApiProps, + apiGatewayProps?: api.RestApiProps): api.RestApi { + // Define the API + let _api: api.RestApi; + if (apiGatewayProps) { + // If property overrides have been provided, incorporate them and deploy + const _apiGatewayProps = overrideProps(defaultApiGatewayProps, apiGatewayProps); + _api = new api.RestApi(scope, 'RestApi', _apiGatewayProps); + } else { + // If no property overrides, deploy using the default configuration + _api = new api.RestApi(scope, 'RestApi', defaultApiGatewayProps); + } + // Configure API access logging + configureApiAccessLogging(scope, _api); + + // Configure Usage Plan + _api.addUsagePlan('UsagePlan', { + apiStages: [{ + api: _api, + stage: _api.deploymentStage + }] + }); + + // Return the API + return _api; +} + +/** + * Builds and returns a global api.RestApi designed to be used with an AWS Lambda function. + * @param scope - the construct to which the RestApi should be attached to. + * @param _existingLambdaObj - an existing AWS Lambda function. + * @param apiGatewayProps - (optional) user-specified properties to override the default properties. + */ +export function GlobalLambdaRestApi(scope: cdk.Construct, _existingLambdaObj: lambda.Function, + apiGatewayProps?: api.LambdaRestApiProps): api.RestApi { + const defaultProps = apiDefaults.DefaultGlobalLambdaRestApiProps(_existingLambdaObj); + return configureLambdaRestApi(scope, defaultProps, apiGatewayProps); +} + +/** + * Builds and returns a regional api.RestApi designed to be used with an AWS Lambda function. + * @param scope - the construct to which the RestApi should be attached to. + * @param _existingLambdaObj - an existing AWS Lambda function. + * @param apiGatewayProps - (optional) user-specified properties to override the default properties. + */ +export function RegionalLambdaRestApi(scope: cdk.Construct, _existingLambdaObj: lambda.Function, + apiGatewayProps?: api.LambdaRestApiProps): api.RestApi { + const defaultProps = apiDefaults.DefaultRegionalLambdaRestApiProps(_existingLambdaObj); + return configureLambdaRestApi(scope, defaultProps, apiGatewayProps); +} + +/** + * Builds and returns a standard api.RestApi. + * @param scope - the construct to which the RestApi should be attached to. + * @param apiGatewayProps - (optional) user-specified properties to override the default properties. + */ +export function GlobalRestApi(scope: cdk.Construct, apiGatewayProps?: api.RestApiProps): api.RestApi { + const defaultProps = apiDefaults.DefaultGlobalApiProps(); + return configureRestApi(scope, defaultProps, apiGatewayProps); +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts new file mode 100644 index 000000000..d23a0e990 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-defaults.ts @@ -0,0 +1,70 @@ +/** + * 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 cloudfront from '@aws-cdk/aws-cloudfront'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as api from '@aws-cdk/aws-apigateway'; +import * as cdk from '@aws-cdk/core'; + +export function DefaultCloudFrontWebDistributionForApiGatewayProps(apiEndPoint: api.RestApi, + loggingBucket?: s3.Bucket): cloudfront.CloudFrontWebDistributionProps { + const apiEndPointUrlWithoutProtocol = cdk.Fn.select(1, cdk.Fn.split("://", apiEndPoint.url)); + const apiEndPointDomainName = cdk.Fn.select(0, cdk.Fn.split("/", apiEndPointUrlWithoutProtocol)); + + if (loggingBucket) { + return { + originConfigs: [{ + customOriginSource: { + domainName: apiEndPointDomainName + }, + behaviors: [{ + isDefaultBehavior: true, + }] + }], + loggingConfig: { + bucket: loggingBucket + } + } as cloudfront.CloudFrontWebDistributionProps; + } else { + return { + originConfigs: [{ + customOriginSource: { + domainName: apiEndPointDomainName + }, + behaviors: [{ + isDefaultBehavior: true, + }] + }] + } as cloudfront.CloudFrontWebDistributionProps; + } +} + +export function DefaultCloudFrontWebDistributionForS3Props(sourceBucket: s3.Bucket, loggingBucket: s3.Bucket, + _originAccessIdentity: cloudfront.IOriginAccessIdentity): + cloudfront.CloudFrontWebDistributionProps { + const cfDistributionProps: cloudfront.CloudFrontWebDistributionProps = { + originConfigs: [ { + s3OriginSource: { + s3BucketSource: sourceBucket, + originAccessIdentity: _originAccessIdentity + }, + behaviors: [ { + isDefaultBehavior: true, + } ] + } ], + loggingConfig: { + bucket: loggingBucket + } + } as cloudfront.CloudFrontWebDistributionProps; + return cfDistributionProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts new file mode 100644 index 000000000..b4c277e9e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/cloudfront-distribution-helper.ts @@ -0,0 +1,126 @@ +/** + * 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 cloudfront from '@aws-cdk/aws-cloudfront'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as iam from '@aws-cdk/aws-iam'; +import * as api from '@aws-cdk/aws-apigateway'; +import { DefaultS3Props } from './s3-bucket-defaults'; +import { DefaultCloudFrontWebDistributionForS3Props, DefaultCloudFrontWebDistributionForApiGatewayProps } from './cloudfront-distribution-defaults'; +import { overrideProps } from './utils'; + +export function CloudFrontDistributionForApiGateway(scope: cdk.Construct, apiEndPoint: api.RestApi, cloudFrontDistributionProps?: + cloudfront.CloudFrontWebDistributionProps | any): cloudfront.CloudFrontWebDistribution { + + let defaultprops; + + if (cloudFrontDistributionProps && cloudFrontDistributionProps.loggingBucket) { + defaultprops = DefaultCloudFrontWebDistributionForApiGatewayProps(apiEndPoint); + } else { + // Create the Logging Bucket + const loggingBucket: s3.Bucket = new s3.Bucket(scope, 'CloudfrontLoggingBucket', DefaultS3Props()); + + // Extract the CfnBucket from the loggingBucket + const loggingBucketResource = loggingBucket.node.findChild('Resource') as s3.CfnBucket; + + // Override accessControl configuration and add metadata for the logging bucket + loggingBucketResource.addPropertyOverride('AccessControl', 'LogDeliveryWrite'); + loggingBucketResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W35', + reason: `This S3 bucket is used as the access logging bucket for CloudFront Distribution` + }, { + id: 'W51', + reason: `This S3 bucket is used as the access logging bucket for CloudFront Distribution` + }] + } + }; + + defaultprops = DefaultCloudFrontWebDistributionForApiGatewayProps(apiEndPoint, loggingBucket); + } + + // Create the Cloudfront Distribution + let cfprops = defaultprops; + if (cloudFrontDistributionProps) { + cfprops = overrideProps(defaultprops, cloudFrontDistributionProps); + } + const cfDistribution: cloudfront.CloudFrontWebDistribution = new cloudfront.CloudFrontWebDistribution(scope, 'CloudFrontDistribution', cfprops); + + return cfDistribution; +} + +export function CloudFrontDistributionForS3(scope: cdk.Construct, sourceBucket: s3.Bucket, cloudFrontDistributionProps?: + cloudfront.CloudFrontWebDistributionProps | any): cloudfront.CloudFrontWebDistribution { + // Create the Logging Bucket + const loggingBucket: s3.Bucket = new s3.Bucket(scope, 'CloudfrontLoggingBucket', DefaultS3Props()); + + // Extract the CfnBucket from the loggingBucket + const loggingBucketResource = loggingBucket.node.findChild('Resource') as s3.CfnBucket; + + // Override accessControl configuration and add metadata for the logging bucket + loggingBucketResource.addPropertyOverride('AccessControl', 'LogDeliveryWrite'); + loggingBucketResource.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W35', + reason: `This S3 bucket is used as the access logging bucket for CloudFront Distribution` + }, { + id: 'W51', + reason: `This S3 bucket is used as the access logging bucket for CloudFront Distribution` + }] + } + }; + + // Create CloudFront Origin Access Identity User + const cfnOrigAccessId = new cloudfront.CfnCloudFrontOriginAccessIdentity(scope, 'CloudFrontOriginAccessIdentity', { + cloudFrontOriginAccessIdentityConfig: { + comment: 'Access S3 bucket content only through CloudFront' + } + }); + + const oaiImported = cloudfront.OriginAccessIdentity.fromOriginAccessIdentityName( + scope, + 'OAIImported', + cfnOrigAccessId.ref + ); + + // Create the Cloudfront Distribution + const defaultprops = DefaultCloudFrontWebDistributionForS3Props(sourceBucket, loggingBucket, oaiImported); + let cfprops = defaultprops; + if (cloudFrontDistributionProps) { + cfprops = overrideProps(defaultprops, cloudFrontDistributionProps); + } + const cfDistribution: cloudfront.CloudFrontWebDistribution = new cloudfront.CloudFrontWebDistribution(scope, 'CloudFrontDistribution', cfprops); + + // Add S3 Bucket Policy to allow s3:GetObject for CloudFront Origin Access Identity User + sourceBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [sourceBucket.arnForObjects('*')], + principals: [new iam.CanonicalUserPrincipal(cfnOrigAccessId.attrS3CanonicalUserId)] + })); + + // Extract the CfnBucketPolicy from the sourceBucket + const bucketPolicy = sourceBucket.policy as s3.BucketPolicy; + const sourceBucketPolicy = bucketPolicy.node.findChild('Resource') as s3.CfnBucketPolicy; + sourceBucketPolicy.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'F16', + reason: `Public website bucket policy requires a wildcard principal` + }] + } + }; + return cfDistribution; +} diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-defaults.ts new file mode 100644 index 000000000..141fe174a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/cloudwatch-log-group-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 logs from '@aws-cdk/aws-logs'; + +export function DefaultLogGroupProps(_logGroupName: string = ''): logs.LogGroupProps { + if (_logGroupName !== '') { + return { + logGroupName: _logGroupName, + retention: logs.RetentionDays.INFINITE + } as logs.LogGroupProps; + } else { + return { + retention: logs.RetentionDays.INFINITE + } as logs.LogGroupProps; + } +} diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.ts new file mode 100644 index 000000000..b761570b1 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/cognito-defaults.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. + */ + +import * as cognito from '@aws-cdk/aws-cognito'; + +const DefaultUserPoolProps: cognito.UserPoolProps = { +}; + +export function DefaultIdentityPoolProps(userPoolClientId: string, userPoolProviderName: string): cognito.CfnIdentityPoolProps { + return { + allowUnauthenticatedIdentities: false, + cognitoIdentityProviders: [{ + clientId: userPoolClientId, + providerName: userPoolProviderName, + serverSideTokenCheck: true + }] + } as cognito.CfnIdentityPoolProps; +} + +export function DefaultUserPoolClientProps(userpool: cognito.UserPool): cognito.UserPoolClientProps { + return { + userPool: userpool + } as cognito.UserPoolClientProps; +} + +export { DefaultUserPoolProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts new file mode 100644 index 000000000..37d0651f1 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/cognito-helper.ts @@ -0,0 +1,118 @@ +/** + * 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 cognito from '@aws-cdk/aws-cognito'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import { overrideProps } from './utils'; +import { DefaultUserPoolProps, DefaultUserPoolClientProps, DefaultIdentityPoolProps } from './cognito-defaults'; + +export interface CognitoOptions { + readonly identitypool: cognito.CfnIdentityPool, + readonly userpool: cognito.UserPool, + readonly userpoolclient: cognito.UserPoolClient +} + +export function buildUserPool(scope: cdk.Construct, userPoolProps?: cognito.UserPoolProps): cognito.UserPool { + let cognitoUserPoolProps: cognito.UserPoolProps; + + if (userPoolProps) { + cognitoUserPoolProps = overrideProps(DefaultUserPoolProps, userPoolProps); + } else { + cognitoUserPoolProps = DefaultUserPoolProps; + } + + const userPool = new cognito.UserPool(scope, 'CognitoUserPool', cognitoUserPoolProps); + + // Set the advancedSecurityMode to ENFORCED + const cfnUserPool = userPool.node.findChild('Resource') as cognito.CfnUserPool; + + cfnUserPool.userPoolAddOns = { + advancedSecurityMode: 'ENFORCED' + }; + + return userPool; +} + +export function buildUserPoolClient(scope: cdk.Construct, userPool: cognito.UserPool, + cognitoUserPoolClientProps?: cognito.UserPoolClientProps): cognito.UserPoolClient { + + let userPoolClientProps: cognito.UserPoolClientProps; + + if (cognitoUserPoolClientProps) { + userPoolClientProps = overrideProps(DefaultUserPoolClientProps(userPool), cognitoUserPoolClientProps); + } else { + userPoolClientProps = DefaultUserPoolClientProps(userPool); + } + + return new cognito.UserPoolClient(scope, 'CognitoUserPoolClient', userPoolClientProps); +} + +export function buildIdentityPool(scope: cdk.Construct, userpool: cognito.UserPool, userpoolclient: cognito.UserPoolClient, + identityPoolProps?: cognito.CfnIdentityPoolProps): cognito.CfnIdentityPool { + + let cognitoIdentityPoolProps: cognito.CfnIdentityPoolProps = DefaultIdentityPoolProps(userpoolclient.userPoolClientId, + userpool.userPoolProviderName); + + if (identityPoolProps) { + cognitoIdentityPoolProps = overrideProps(cognitoIdentityPoolProps, identityPoolProps); + } + + const idPool = new cognito.CfnIdentityPool(scope, 'CognitoIdentityPool', cognitoIdentityPoolProps); + + return idPool; +} + +export function setupCognitoForElasticSearch(scope: cdk.Construct, domainName: string, options: CognitoOptions): iam.Role { + + // Create the domain for Cognito UserPool + const userpooldomain = new cognito.CfnUserPoolDomain(scope, 'UserPoolDomain', { + domain: domainName, + userPoolId: options.userpool.userPoolId + }); + userpooldomain.addDependsOn(options.userpool.node.findChild('Resource') as cognito.CfnUserPool); + + // Setup the IAM Role for Cognito Authorized Users + const cognitoPrincipal = new iam.FederatedPrincipal( + 'cognito-identity.amazonaws.com', + { + 'StringEquals': { 'cognito-identity.amazonaws.com:aud': options.identitypool.ref }, + 'ForAnyValue:StringLike': { 'cognito-identity.amazonaws.com:amr': 'authenticated' } + }, + 'sts:AssumeRoleWithWebIdentity'); + + const cognitoAuthorizedRole = new iam.Role(scope, 'CognitoAuthorizedRole', { + assumedBy: cognitoPrincipal, + inlinePolicies: { + CognitoAccessPolicy: new iam.PolicyDocument({ + statements: [new iam.PolicyStatement({ + actions: [ + 'es:ESHttp*' + ], + resources: [`arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${domainName}/*`] + }) + ] + }) + } + }); + + // Attach the IAM Role for Cognito Authorized Users + new cognito.CfnIdentityPoolRoleAttachment(scope, 'IdentityPoolRoleMapping', { + identityPoolId: options.identitypool.ref, + roles: { + authenticated: cognitoAuthorizedRole.roleArn + } + }); + + return cognitoAuthorizedRole; +} diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts new file mode 100644 index 000000000..a3921a915 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/dynamodb-table-defaults.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as dynamodb from '@aws-cdk/aws-dynamodb'; + +const DefaultTableProps: dynamodb.TableProps = { + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + serverSideEncryption: true, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + } +}; + +const DefaultTableWithStreamProps: dynamodb.TableProps = { + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + serverSideEncryption: true, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + }, + stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES +}; + +export { DefaultTableProps, DefaultTableWithStreamProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts new file mode 100644 index 000000000..e9f5b0190 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-defaults.ts @@ -0,0 +1,80 @@ +/** + * 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 elasticsearch from '@aws-cdk/aws-elasticsearch'; +import * as cognito from '@aws-cdk/aws-cognito'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; + +export interface CfnDomainOptions { + readonly identitypool: cognito.CfnIdentityPool, + readonly userpool: cognito.UserPool, + readonly cognitoAuthorizedRoleARN: string, + readonly serviceRoleARN?: string +} + +export function DefaultCfnDomainProps(domainName: string, cognitoKibanaConfigureRole: iam.Role, options: CfnDomainOptions) { + const roleARNs: iam.IPrincipal[] = []; + + roleARNs.push(new iam.ArnPrincipal(options.cognitoAuthorizedRoleARN)); + + if (options.serviceRoleARN) { + roleARNs.push(new iam.ArnPrincipal(options.serviceRoleARN)); + } + + return { + domainName, + elasticsearchVersion: '6.3', + encryptionAtRestOptions: { + enabled: true + }, + nodeToNodeEncryptionOptions: { + enabled: true + }, + elasticsearchClusterConfig: { + dedicatedMasterEnabled: true, + dedicatedMasterCount: 3, + instanceCount: 3, + zoneAwarenessEnabled: true, + zoneAwarenessConfig: { + availabilityZoneCount: 3 + } + }, + snapshotOptions: { + automatedSnapshotStartHour: 1 + }, + ebsOptions: { + ebsEnabled: true, + volumeSize: 10 + }, + cognitoOptions: { + enabled: true, + identityPoolId: options.identitypool.ref, + userPoolId: options.userpool.userPoolId, + roleArn: cognitoKibanaConfigureRole.roleArn + }, + accessPolicies: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + principals: roleARNs, + actions: [ + 'es:ESHttp*' + ], + resources: [ + `arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${domainName}/*` + ] + }) + ] + }) + } as elasticsearch.CfnDomainProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts new file mode 100644 index 000000000..2b5c8aab1 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/elasticsearch-helper.ts @@ -0,0 +1,218 @@ +/** + * 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 elasticsearch from '@aws-cdk/aws-elasticsearch'; +import { CfnDomainOptions, DefaultCfnDomainProps } from './elasticsearch-defaults'; +import { overrideProps } from './utils'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; +import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; + +export function buildElasticSearch(scope: cdk.Construct, domainName: string, + options: CfnDomainOptions, cfnDomainProps?: elasticsearch.CfnDomainProps): elasticsearch.CfnDomain { + + // Setup the IAM Role & policy for ES to configure Cognito User pool and Identity pool + const cognitoKibanaConfigureRole = new iam.Role(scope, 'CognitoKibanaConfigureRole', { + assumedBy: new iam.ServicePrincipal('es.amazonaws.com') + }); + + const cognitoKibanaConfigureRolePolicy = new iam.Policy(scope, 'CognitoKibanaConfigureRolePolicy', { + statements: [ + new iam.PolicyStatement({ + actions: [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig" + ], + resources: [ + options.userpool.userPoolArn, + `arn:aws:cognito-identity:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:identitypool/${options.identitypool.ref}`, + `arn:aws:es:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:domain/${domainName}` + ] + }), + new iam.PolicyStatement({ + actions: [ + "iam:PassRole" + ], + conditions: { + StringLike: {'iam:PassedToService': 'cognito-identity.amazonaws.com'} + }, + resources: [ + cognitoKibanaConfigureRole.roleArn + ] + }) + ] + }); + cognitoKibanaConfigureRolePolicy.attachToRole(cognitoKibanaConfigureRole); + + let _cfnDomainProps = DefaultCfnDomainProps(domainName, cognitoKibanaConfigureRole, options); + + if (cfnDomainProps) { + _cfnDomainProps = overrideProps(_cfnDomainProps, cfnDomainProps); + } + + const esDomain = new elasticsearch.CfnDomain(scope, 'ElasticsearchDomain', _cfnDomainProps); + + esDomain.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W28', + reason: `The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only` + }] + } + }; + + return esDomain; +} + +export function buildElasticSearchCWAlarms(scope: cdk.Construct): cloudwatch.Alarm[] { + // Setup CW Alarms for ES + const alarms: cloudwatch.Alarm[] = new Array(); + + // ClusterStatus.red maximum is >= 1 for 1 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'StatusRedAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'ClusterStatus.red' + }), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Maximum', + period: cdk.Duration.seconds(60), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'At least one primary shard and its replicas are not allocated to a node. ' + })); + + // ClusterStatus.yellow maximum is >= 1 for 1 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'StatusYellowAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'ClusterStatus.yellow' + }), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Maximum', + period: cdk.Duration.seconds(60), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'At least one replica shard is not allocated to a node.' + })); + + // FreeStorageSpace minimum is <= 20480 for 1 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'FreeStorageSpaceTooLowAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'FreeStorageSpace' + }), + threshold: 2000, + evaluationPeriods: 1, + statistic: 'Minimum', + period: cdk.Duration.seconds(60), + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'A node in your cluster is down to 20 GiB of free storage space.' + })); + + // ClusterIndexWritesBlocked is >= 1 for 5 minutes, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'IndexWritesBlockedTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'ClusterIndexWritesBlocked' + }), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Maximum', + period: cdk.Duration.seconds(300), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Your cluster is blocking write requests.' + })); + + // AutomatedSnapshotFailure maximum is >= 1 for 1 minute, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'AutomatedSnapshotFailureTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'AutomatedSnapshotFailure' + }), + threshold: 1, + evaluationPeriods: 1, + statistic: 'Maximum', + period: cdk.Duration.seconds(60), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'An automated snapshot failed. This failure is often the result of a red cluster health status.' + })); + + // CPUUtilization maximum is >= 80% for 15 minutes, 3 consecutive times + alarms.push(new cloudwatch.Alarm(scope, 'CPUUtilizationTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'CPUUtilization' + }), + threshold: 80, + evaluationPeriods: 3, + statistic: 'Average', + period: cdk.Duration.seconds(900), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: '100% CPU utilization is not uncommon, but sustained high usage is problematic. Consider using larger instance types or adding instances.' + })); + + // JVMMemoryPressure maximum is >= 80% for 5 minutes, 3 consecutive times + alarms.push(new cloudwatch.Alarm(scope, 'JVMMemoryPressureTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'JVMMemoryPressure' + }), + threshold: 80, + evaluationPeriods: 1, + statistic: 'Average', + period: cdk.Duration.seconds(900), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.' + })); + + // MasterCPUUtilization maximum is >= 50% for 15 minutes, 3 consecutive times + alarms.push(new cloudwatch.Alarm(scope, 'MasterCPUUtilizationTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'MasterCPUUtilization' + }), + threshold: 50, + evaluationPeriods: 3, + statistic: 'Average', + period: cdk.Duration.seconds(900), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Average CPU utilization over last 45 minutes too high. Consider using larger instance types for your dedicated master nodes.' + })); + + // MasterJVMMemoryPressure maximum is >= 80% for 15 minutes, 1 consecutive time + alarms.push(new cloudwatch.Alarm(scope, 'MasterJVMMemoryPressureTooHighAlarm', { + metric: new cloudwatch.Metric({ + namespace: 'AWS/ES', + metricName: 'MasterJVMMemoryPressure' + }), + threshold: 50, + evaluationPeriods: 1, + statistic: 'Average', + period: cdk.Duration.seconds(900), + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, + alarmDescription: 'Average JVM memory pressure over last 15 minutes too high. Consider scaling vertically.' + })); + + return alarms; +} diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts new file mode 100644 index 000000000..2d34ca3b6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/events-rule-defaults.ts @@ -0,0 +1,22 @@ +/** + * 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 events from '@aws-cdk/aws-events'; + +export function DefaultEventsRuleProps(_targets: events.IRuleTarget[]) { + const defaultEventsRuleProps: events.RuleProps = { + targets: _targets + }; + + return defaultEventsRuleProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts new file mode 100644 index 000000000..20c1173a2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/iot-topic-rule-defaults.ts @@ -0,0 +1,28 @@ +/** + * 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 iot from '@aws-cdk/aws-iot'; + +export function DefaultCfnTopicRuleProps(_actions: iot.CfnTopicRule.ActionProperty[], _sql: string = '') { + const _topicRulePayload: iot.CfnTopicRule.TopicRulePayloadProperty = { + ruleDisabled: false, + actions: _actions, + sql: _sql + }; + + const defaultCfnTopicRuleProps: iot.CfnTopicRuleProps = { + topicRulePayload: _topicRulePayload + }; + + return defaultCfnTopicRuleProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts new file mode 100644 index 000000000..a70ac5ada --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-defaults.ts @@ -0,0 +1,20 @@ +/** + * 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 kinesisanalytics from '@aws-cdk/aws-kinesisanalytics'; + +const DefaultCfnApplicationProps: kinesisanalytics.CfnApplicationProps = { + inputs: [] +}; + +export { DefaultCfnApplicationProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts new file mode 100644 index 000000000..aec263367 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-analytics-helper.ts @@ -0,0 +1,72 @@ +/** + * 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 * as kinesisAnalytics from '@aws-cdk/aws-kinesisanalytics'; + import * as kinesisFirehose from '@aws-cdk/aws-kinesisfirehose'; + import * as iam from '@aws-cdk/aws-iam'; + import * as defaults from './kinesis-analytics-defaults'; + import * as cdk from '@aws-cdk/core'; + import { overrideProps } from './utils'; + + export interface BuildKinesisAnalyticsAppProps { + /** + * A Kinesis Data Firehose for the Kinesis Streams application to connect to. + * + * @default - Default props are used + */ + readonly kinesisFirehose: kinesisFirehose.CfnDeliveryStream + /** + * Optional user provided props to override the default props for the Kinesis analytics app. + * + * @default - Default props are used + */ + readonly kinesisAnalyticsProps?: kinesisAnalytics.CfnApplicationProps | any + } + + export function buildKinesisAnalyticsApp(scope: cdk.Construct, props: BuildKinesisAnalyticsAppProps): kinesisAnalytics.CfnApplication { + + // Setup the IAM role for Kinesis Analytics + const analyticsRole = new iam.Role(scope, 'KinesisAnalyticsRole', { + assumedBy: new iam.ServicePrincipal('kinesisanalytics.amazonaws.com'), + }); + + // Setup the IAM policy for Kinesis Analytics + const analyticsPolicy = new iam.Policy(scope, 'KinesisAnalyticsPolicy', { + statements: [ + new iam.PolicyStatement({ + actions: [ + 'firehose:DescribeDeliveryStream', + 'firehose:Get*' + ], + resources: [props.kinesisFirehose.attrArn] + }) + ]}); + + // Attach policy to role + analyticsPolicy.attachToRole(analyticsRole); + + // Setup the Kinesis application properties + const kinesisAnalyticsProps = overrideProps(defaults.DefaultCfnApplicationProps, props.kinesisAnalyticsProps); + kinesisAnalyticsProps.inputs[0].kinesisFirehoseInput = { + resourceArn: props.kinesisFirehose.attrArn, + roleArn: analyticsRole.roleArn + }; + + // Setup the Kinesis application and add dependencies + const kinesisAnalyticsApp = new kinesisAnalytics.CfnApplication(scope, 'KinesisAnalytics', kinesisAnalyticsProps); + kinesisAnalyticsApp.addDependsOn(analyticsPolicy.node.findChild('Resource') as iam.CfnPolicy); + + // Create the application and return + return kinesisAnalyticsApp; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts new file mode 100644 index 000000000..3b478853e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-firehose-s3-defaults.ts @@ -0,0 +1,34 @@ +/** + * 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 { CfnDeliveryStreamProps } from '@aws-cdk/aws-kinesisfirehose'; + +export function DefaultCfnDeliveryStreamProps(_bucketArn: string, _roleArn: string, + _logGroupName: string, _logStreamName: string): CfnDeliveryStreamProps { + return { + extendedS3DestinationConfiguration : { + bucketArn: _bucketArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5 + }, + compressionFormat: 'GZIP', + roleArn: _roleArn, + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: _logGroupName, + logStreamName: _logStreamName + } + } + } as CfnDeliveryStreamProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts new file mode 100644 index 000000000..ef20c82d8 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-defaults.ts @@ -0,0 +1,20 @@ +/** + * 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 kinesis from '@aws-cdk/aws-kinesis'; + +const DefaultStreamProps: kinesis.StreamProps = { + encryption: kinesis.StreamEncryption.KMS +}; + +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-konstruk/core/lib/kinesis-streams-helper.ts new file mode 100644 index 000000000..0f91ae987 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kinesis-streams-helper.ts @@ -0,0 +1,54 @@ +/** + * 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 * 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. + * + * @default - Default props are used. + */ + readonly kinesisStreamProps?: kinesis.StreamProps | any +} + +export function buildKinesisStream(scope: cdk.Construct, props?: BuildKinesisStreamProps): kinesis.Stream { + // If props is undefined, define it + props = (props === undefined) ? {} : props; + // Setup the stream properties + let kinesisStreamProps; + if (props.hasOwnProperty('kinesisStreamProps')) { + // If property overrides have been provided, incorporate them and deploy + kinesisStreamProps = overrideProps(DefaultStreamProps, props.kinesisStreamProps); + } else { + // 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-konstruk/core/lib/kms-defaults.ts new file mode 100644 index 000000000..589eb8d22 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kms-defaults.ts @@ -0,0 +1,20 @@ +/** + * 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 { KeyProps } from '@aws-cdk/aws-kms'; + +const DefaultEncryptionProps: KeyProps = { + enableKeyRotation: true +}; + +export { DefaultEncryptionProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts new file mode 100644 index 000000000..40678d57c --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/kms-helper.ts @@ -0,0 +1,43 @@ +/** + * 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 * as kms from '@aws-cdk/aws-kms'; +import { DefaultEncryptionProps } from './kms-defaults'; +import * as cdk from '@aws-cdk/core'; +import { overrideProps } from './utils'; + +export interface BuildEncryptionKeyProps { + /** + * Optional user-provided props to override the default props for the encryption key. + * + * @default - Default props are used. + */ + readonly encryptionKeyProps?: kms.KeyProps | any +} + +export function buildEncryptionKey(scope: cdk.Construct, props?: BuildEncryptionKeyProps): kms.Key { + // If props is undefined, define it + props = (props === undefined) ? {} : props; + // Setup the key properties + let encryptionKeyProps; + if (props.hasOwnProperty('encryptionKeyProps')) { + // If property overrides have been provided, incorporate them and deploy + encryptionKeyProps = overrideProps(DefaultEncryptionProps, props.encryptionKeyProps); + } else { + // If no property overrides, deploy using the default configuration + encryptionKeyProps = DefaultEncryptionProps; + } + // Create the encryption key and return + return new kms.Key(scope, 'EncryptionKey', encryptionKeyProps); +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.ts new file mode 100644 index 000000000..8ecd5934f --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-defaults.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. + */ + +import * as lambda from '@aws-cdk/aws-lambda'; +import * as iam from '@aws-cdk/aws-iam'; + +export function DefaultLambdaFunctionProps(lambdaServiceRole: iam.Role): lambda.FunctionProps | any { + + const lambdaFunctionProps: lambda.FunctionProps | any = { + role: lambdaServiceRole + }; + + return lambdaFunctionProps; +} + +export function DefaultLambdaFunctionPropsForNodeJS(lambdaServiceRole: iam.Role): lambda.FunctionProps | any { + + const lambdaFunctionProps: lambda.FunctionProps | any = { + role: lambdaServiceRole, + environment: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' + } + }; + + return lambdaFunctionProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.ts new file mode 100644 index 000000000..1672b7c89 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-event-source-mapping-defaults.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. + */ + +import * as lambda from '@aws-cdk/aws-lambda'; +import { overrideProps } from './utils'; +import { DynamoEventSourceProps, S3EventSourceProps } from '@aws-cdk/aws-lambda-event-sources'; +import * as s3 from '@aws-cdk/aws-s3'; + +export function DefaultKinesisEventSourceProps(_eventSourceArn: string) { + const defaultEventSourceProps: lambda.EventSourceMappingOptions = { + eventSourceArn: _eventSourceArn + }; + return defaultEventSourceProps; +} + +export function DynamoEventSourceProps(_dynamoEventSourceProps?: DynamoEventSourceProps) { + + const defaultDynamoEventSourceProps = { + startingPosition: lambda.StartingPosition.TRIM_HORIZON + }; + + if (_dynamoEventSourceProps) { + return overrideProps(defaultDynamoEventSourceProps, _dynamoEventSourceProps); + } else { + return defaultDynamoEventSourceProps; + } +} + +export function S3EventSourceProps(_s3EventSourceProps?: S3EventSourceProps) { + + const defaultS3EventSourceProps: S3EventSourceProps = { + events: [s3.EventType.OBJECT_CREATED] + }; + + if (_s3EventSourceProps) { + return overrideProps(defaultS3EventSourceProps, _s3EventSourceProps, false); + } else { + return defaultS3EventSourceProps; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts new file mode 100644 index 000000000..c7e48e9ab --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/lambda-helper.ts @@ -0,0 +1,103 @@ +/** + * 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 lambda from '@aws-cdk/aws-lambda'; +import * as iam from '@aws-cdk/aws-iam'; +import { DefaultLambdaFunctionProps, DefaultLambdaFunctionPropsForNodeJS } from './lambda-defaults'; +import * as cdk from '@aws-cdk/core'; +import { overrideProps } from './utils'; + +export interface BuildLambdaFunctionProps { + /** + * Whether to create a new Lambda function or use an existing Lambda function. + * If set to false, you must provide a lambda function object as `existingLambdaObj` + * + * @default - true + */ + readonly deployLambda: boolean, + /** + * Existing instance of Lambda Function object. + * If `deploy` is set to false only then this property is required + * + * @default - None + */ + readonly existingLambdaObj?: lambda.Function, + /** + * Optional user provided props to override the default props for the Lambda function. + * If `deploy` is set to true only then this property is required + * + * @default - Default props are used + */ + readonly lambdaFunctionProps?: lambda.FunctionProps +} + +export function buildLambdaFunction(scope: cdk.Construct, props: BuildLambdaFunctionProps): lambda.Function { + // Conditional lambda function creation + // If deployLambda == false + if (props.hasOwnProperty('deployLambda') && props.deployLambda === false) { + if (props.existingLambdaObj) { + return props.existingLambdaObj; + } else { + throw Error('Missing existingObj from props for deployLambda = false'); + } + // If deployLambda == true + } else { + if (props.lambdaFunctionProps) { + return deployLambdaFunction(scope, props.lambdaFunctionProps); + } else { + throw Error('Missing lambdaFunctionProps from props for deployLambda = true'); + } + } +} + +export function deployLambdaFunction(scope: cdk.Construct, lambdaFunctionProps: lambda.FunctionProps): lambda.Function { + // Setup the IAM Role for Lambda Service + const lambdaServiceRole = new iam.Role(scope, 'LambdaFunctionServiceRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + inlinePolicies: { + LambdaFunctionServiceRolePolicy: new iam.PolicyDocument({ + statements: [new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents' + ], + resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`] + })] + }) + } + }); + + // Override the DefaultFunctionProps with user provided lambdaFunctionProps + let _lambdaFunctionProps = overrideProps(DefaultLambdaFunctionProps(lambdaServiceRole), lambdaFunctionProps); + + if (lambdaFunctionProps.runtime === lambda.Runtime.NODEJS_10_X || + lambdaFunctionProps.runtime === lambda.Runtime.NODEJS_12_X) { + _lambdaFunctionProps = overrideProps(DefaultLambdaFunctionPropsForNodeJS(lambdaServiceRole), lambdaFunctionProps); + } + + const lambdafunction = new lambda.Function(scope, 'LambdaFunction', _lambdaFunctionProps); + + const cfnLambdafunction = lambdafunction.node.findChild('Resource') as lambda.CfnFunction; + + cfnLambdafunction.cfnOptions.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.` + }] + } + }; + + return lambdafunction; +} diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts new file mode 100644 index 000000000..cc57e5ed3 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-defaults.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as s3 from '@aws-cdk/aws-s3'; +import { RemovalPolicy } from '@aws-cdk/core'; +import { Bucket, BucketProps } from '@aws-cdk/aws-s3'; + +export function DefaultS3Props(loggingBucket ?: Bucket): s3.BucketProps { + if (loggingBucket) { + return { + encryption: s3.BucketEncryption.S3_MANAGED, + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: RemovalPolicy.RETAIN, + serverAccessLogsBucket: loggingBucket + } as BucketProps; + } else { + return { + encryption: s3.BucketEncryption.S3_MANAGED, + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: RemovalPolicy.RETAIN + } as BucketProps; + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.ts new file mode 100644 index 000000000..4a01a1df7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/s3-bucket-helper.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 * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { DefaultS3Props } from './s3-bucket-defaults'; +import { overrideProps } from './utils'; + +export interface BuildS3BucketProps { + /** + * 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 +} + +export function buildS3Bucket(scope: cdk.Construct, props: BuildS3BucketProps): s3.Bucket { + // Conditional s3 Bucket creation + // If deployBucket == false + if (props.hasOwnProperty('deployBucket') && props.deployBucket === false) { + if (props.existingBucketObj) { + return props.existingBucketObj; + } else { + throw Error('Missing existingBucketObj from props for deployBucket = false'); + } + // If deploy == true + } else { + if (props.bucketProps) { + return s3BucketWithLogging(scope, props.bucketProps); + } else { + return s3BucketWithLogging(scope, DefaultS3Props()); + } + } +} + +function s3BucketWithLogging(scope: cdk.Construct, s3BucketProps?: s3.BucketProps): s3.Bucket { + + // Create the Application Bucket + let bucketprops; + + if (s3BucketProps?.serverAccessLogsBucket) { + bucketprops = DefaultS3Props; + } else { + // Create the Logging Bucket + const loggingBucket: s3.Bucket = new s3.Bucket(scope, 'S3LoggingBucket', DefaultS3Props()); + + // Extract the CfnBucket from the loggingBucket + const loggingBucketResource = loggingBucket.node.findChild('Resource') as s3.CfnBucket; + + // Override accessControl configuration and add metadata for the logging bucket + loggingBucketResource.addPropertyOverride('AccessControl', 'LogDeliveryWrite'); + loggingBucketResource.cfnOptions.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` + }] + } + }; + bucketprops = DefaultS3Props(loggingBucket); + } + + if (s3BucketProps) { + bucketprops = overrideProps(bucketprops, s3BucketProps); + } + const s3Bucket: s3.Bucket = new s3.Bucket(scope, 'S3Bucket', 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-konstruk/core/lib/sns-defaults.ts new file mode 100644 index 000000000..fd88fbf50 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/sns-defaults.ts @@ -0,0 +1,18 @@ +/** + * 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 sns from '@aws-cdk/aws-sns'; + +const DefaultSnsTopicProps: sns.TopicProps = {}; + +export { DefaultSnsTopicProps }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts new file mode 100644 index 000000000..0056bc5ac --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/sns-helper.ts @@ -0,0 +1,66 @@ +/** + * 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 * as sns from '@aws-cdk/aws-sns'; +import * as kms from '@aws-cdk/aws-kms'; +import { DefaultSnsTopicProps } from './sns-defaults'; +import { buildEncryptionKey } from './kms-helper'; +import * as cdk from '@aws-cdk/core'; +import { overrideProps } from './utils'; + +export interface BuildTopicProps { + /** + * Optional user provided props to override the default props for the SNS topic. + * + * @default - Default props are used. + */ + readonly topicProps?: sns.TopicProps | any + /** + * Use a KMS Key, either managed by this CDK app, or imported. If importing an encryption key, it must be specified in + * the encryptionKey property for this construct. + * + * @default - true (encryption enabled, managed by this CDK app). + */ + readonly enableEncryption?: boolean + /** + * An optional, imported encryption key to encrypt the SNS topic with. + * + * @default - not specified. + */ + readonly encryptionKey?: kms.Key +} + +export function buildTopic(scope: cdk.Construct, props?: BuildTopicProps): sns.Topic { + // If props is undefined, define it + props = (props === undefined) ? {} : props; + // Setup the topic properties + let snsTopicProps; + if (props.hasOwnProperty('topicProps')) { + // If property overrides have been provided, incorporate them and deploy + snsTopicProps = overrideProps(DefaultSnsTopicProps, props.topicProps); + } else { + // If no property overrides, deploy using the default configuration + snsTopicProps = DefaultSnsTopicProps; + } + // Set encryption properties + if (!props.enableEncryption || props.enableEncryption === true) { + if (props.encryptionKey) { + snsTopicProps.masterKey = props.encryptionKey; + } else { + snsTopicProps.masterKey = buildEncryptionKey(scope); + } + } + // Create the stream and return + return new sns.Topic(scope, 'SnsTopic', snsTopicProps); +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts b/source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts new file mode 100644 index 000000000..9df6b1f21 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/sqs-defaults.ts @@ -0,0 +1,23 @@ +/** + * 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 sqs from '@aws-cdk/aws-sqs'; +import * as kms from '@aws-cdk/aws-kms'; + +export function DefaultQueueProps(_encryptionMasterKey?: kms.Key) { + const _DefaultQueueProps: sqs.QueueProps = { + encryption: sqs.QueueEncryption.KMS, + encryptionMasterKey: _encryptionMasterKey + }; + return _DefaultQueueProps; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts b/source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts new file mode 100644 index 000000000..b22d454e5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/sqs-helper.ts @@ -0,0 +1,84 @@ +/** + * 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 * 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. + * + * @default - Default props are used. + */ + readonly queueProps?: sqs.QueueProps | any + /** + * Optional dead letter queue to pass bad requests to after the max receive count is reached. + * + * @default - Default props are used. + */ + readonly deadLetterQueue?: sqs.DeadLetterQueue +} + +export function buildQueue(scope: cdk.Construct, id: string, props?: BuildQueueProps): sqs.Queue { + // If props is undefined, define it + props = (props === undefined) ? {} : props; + // Setup the queue + let queueProps; + if (props.queueProps) { + // If property overrides have been provided, incorporate them and deploy + queueProps = overrideProps(defaults.DefaultQueueProps(props.encryptionKey), props.queueProps); + } else { + // If no property overrides, deploy using the default configuration + queueProps = defaults.DefaultQueueProps(props.encryptionKey); + } + // Determine whether a DLQ property should be added + if (props.deadLetterQueue) { + queueProps.deadLetterQueue = props.deadLetterQueue; + } + // Return the queue + return new sqs.Queue(scope, id, queueProps); +} + +export interface BuildDeadLetterQueueProps { + /** + * An existing queue that has already been defined to be used as the dead letter queue. + * + * @default - Default props are used. + */ + readonly deadLetterQueue: sqs.Queue + /** + * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. + * + * @default - Default props are used + */ + readonly maxReceiveCount: number +} + +export function buildDeadLetterQueue(props: BuildDeadLetterQueueProps): sqs.DeadLetterQueue { + // Setup the queue interface and return + const dlq: sqs.DeadLetterQueue = { + maxReceiveCount: props.maxReceiveCount, + queue: props.deadLetterQueue + }; + return dlq; +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/lib/utils.ts b/source/patterns/@aws-solutions-konstruk/core/lib/utils.ts new file mode 100644 index 000000000..5dd07a3a7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/lib/utils.ts @@ -0,0 +1,74 @@ +/** + * 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 deepmerge from 'deepmerge'; + +function isObject(val: object) { + return val != null && typeof val === 'object' + && Object.prototype.toString.call(val) === '[object Object]'; +} + +function isPlainObject(o: object) { + if (Array.isArray(o) === true) { + return true; + } + + if (isObject(o) === false) { + return false; + } + + // If has modified constructor + const ctor = o.constructor; + if (typeof ctor !== 'function') { + return false; + } + + // If has modified prototype + const prot = ctor.prototype; + if (isObject(prot) === false) { + return false; + } + + // If constructor does not have an Object-specific method + if (prot.hasOwnProperty('isPrototypeOf') === false) { + return false; + } + + // Most likely a plain Object + return true; +} + +function combineMerge(target: any[], source: any[]) { + return target.concat(source); +} + +function overwriteMerge(target: any[], source: any[]) { + target = source; + return target; +} + +export function overrideProps(DefaultProps: object, userProps: object, concatArray: boolean = false): any { + // Override the sensible defaults with user provided props + + if (concatArray) { + return deepmerge(DefaultProps, userProps, { + arrayMerge: combineMerge, + isMergeableObject: isPlainObject + }); + } else { + return deepmerge(DefaultProps, userProps, { + arrayMerge: overwriteMerge, + isMergeableObject: isPlainObject + }); + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/package.json b/source/patterns/@aws-solutions-konstruk/core/package.json new file mode 100644 index 000000000..bc9a3465e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/package.json @@ -0,0 +1,112 @@ +{ + "name": "@aws-solutions-konstruk/core", + "version": "0.8.0", + "description": "Core CDK Construct for patterns library", + "main": "index.js", + "types": "index.d.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.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/aws-iot": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kinesisanalytics": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/aws-sns": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-events": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0", + "deepmerge": "^4.0.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "bundledDependencies": [ + "deepmerge" + ], + "peerDependencies": { + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-cdk/aws-iot": "~1.25.0", + "@aws-cdk/aws-kinesis": "~1.25.0", + "@aws-cdk/aws-kinesisanalytics": "~1.25.0", + "@aws-cdk/aws-kinesisfirehose": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-lambda-event-sources": "~1.25.0", + "@aws-cdk/aws-logs": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/aws-sns": "~1.25.0", + "@aws-cdk/aws-sqs": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-kms": "~1.25.0", + "@aws-cdk/aws-events": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-elasticsearch": "~1.25.0", + "@aws-cdk/aws-cloudwatch": "~1.25.0" + } +} diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap new file mode 100644 index 000000000..08a773a2f --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/apigateway-helper.test.js.snap @@ -0,0 +1,1128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test default RestApi deployment w/ ApiGatewayProps 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "customRestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiDeployment180EC50321995420db12611041100554516080b6": Object { + "DependsOn": Array [ + "RestApiapigatewayresourcePOST2678115A", + "RestApiapigatewayresource242D19A1", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "ApiAccessLogGroupCEA70788", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC50321995420db12611041100554516080b6", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiapigatewayresource242D19A1": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "api-gateway-resource", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "RestApiapigatewayresourcePOST2678115A": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "POST", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "ResponseTemplates": Object { + "text/html": "Success", + }, + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/x-www-form-urlencoded": "Action=SendMessage&MessageBody=$util.urlEncode(\\"$input.body\\")&MessageAttribute.1.Name=queryParam1&MessageAttribute.1.Value.StringValue=$input.params(\\"query_param_1\\")&MessageAttribute.1.Value.DataType=String", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/12345678/thisqueuequeueName", + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "RequestParameters": Object { + "method.request.querystring.query_param_1": true, + }, + "ResourceId": Object { + "Ref": "RestApiapigatewayresource242D19A1", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + }, +} +`; + +exports[`Test default RestApi deployment w/o ApiGatewayProps 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiDeployment180EC50321995420db12611041100554516080b6": Object { + "DependsOn": Array [ + "RestApiapigatewayresourcePOST2678115A", + "RestApiapigatewayresource242D19A1", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "ApiAccessLogGroupCEA70788", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC50321995420db12611041100554516080b6", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiapigatewayresource242D19A1": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "api-gateway-resource", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "RestApiapigatewayresourcePOST2678115A": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "POST", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "IntegrationResponses": Array [ + Object { + "ResponseTemplates": Object { + "text/html": "Success", + }, + "StatusCode": "200", + }, + Object { + "ResponseTemplates": Object { + "text/html": "Error", + }, + "SelectionPattern": "500", + "StatusCode": "500", + }, + ], + "PassthroughBehavior": "NEVER", + "RequestParameters": Object { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'", + }, + "RequestTemplates": Object { + "application/x-www-form-urlencoded": "Action=SendMessage&MessageBody=$util.urlEncode(\\"$input.body\\")&MessageAttribute.1.Name=queryParam1&MessageAttribute.1.Value.StringValue=$input.params(\\"query_param_1\\")&MessageAttribute.1.Value.DataType=String", + }, + "Type": "AWS", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":sqs:path/12345678/thisqueuequeueName", + ], + ], + }, + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "200", + }, + Object { + "ResponseParameters": Object { + "method.response.header.Content-Type": true, + }, + "StatusCode": "500", + }, + ], + "RequestParameters": Object { + "method.request.querystring.query_param_1": true, + }, + "ResourceId": Object { + "Ref": "RestApiapigatewayresource242D19A1", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + }, +} +`; + +exports[`snapshot test RegionalApiGateway default params 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "REGIONAL", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionRestApiANY3A99B4EE": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestRestApiANY79BD91F2": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "ApiAccessLogGroupCEA70788", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC503c4d635ab14db0b242903e058a000f3f8", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionRestApiANYproxy9C9912F9": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestRestApiANYproxyCB7BC56D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap new file mode 100644 index 000000000..4dea3d1d2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-api-gateway-helper.test.js.snap @@ -0,0 +1,608 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cloudfront distribution for ApiGateway with default params 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "CloudFrontDistributionCFDistribution599ADCC4": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "CloudfrontLoggingBucket3C3EFAA7", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "CustomOriginConfig": Object { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": Array [ + "TLSv1.2", + ], + }, + "DomainName": Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "/", + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "://", + Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + "Id": "origin1", + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "CloudfrontLoggingBucket3C3EFAA7": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleC555A460", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRoleC555A460", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRoleC555A460": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionRestApiANY3A99B4EE": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestRestApiANY79BD91F2": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiAccount7C83CF5A": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "RestApiCloudWatchRoleE3ED6605", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "RestApiCloudWatchRoleE3ED6605": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "RestApiDeployment180EC503d2c6df3c8dc8b7193b98c1a0bff4e677": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + ], + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC503d2c6df3c8dc8b7193b98c1a0bff4e677", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionRestApiANYproxy9C9912F9": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestRestApiANYproxyCB7BC56D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap new file mode 100644 index 000000000..56e065544 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudfront-distribution-s3-helper.test.js.snap @@ -0,0 +1,289 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cloudfront distribution with default params 1`] = ` +Object { + "Resources": Object { + "CloudFrontDistributionCFDistribution599ADCC4": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "CloudfrontLoggingBucket3C3EFAA7", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "DomainName": Object { + "Fn::GetAtt": Array [ + "S3Bucket07682993", + "RegionalDomainName", + ], + }, + "Id": "origin1", + "S3OriginConfig": Object { + "OriginAccessIdentity": Object { + "Fn::Join": Array [ + "", + Array [ + "origin-access-identity/cloudfront/", + Object { + "Ref": "CloudFrontOriginAccessIdentity", + }, + ], + ], + }, + }, + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "CloudFrontOriginAccessIdentity": Object { + "Properties": Object { + "CloudFrontOriginAccessIdentityConfig": Object { + "Comment": "Access S3 bucket content only through CloudFront", + }, + }, + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + }, + "CloudfrontLoggingBucket3C3EFAA7": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "S3Bucket07682993": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "S3LoggingBucket800A2B27", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "S3BucketPolicyF560589A": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "F16", + "reason": "Public website bucket policy requires a wildcard principal", + }, + ], + }, + }, + "Properties": Object { + "Bucket": Object { + "Ref": "S3Bucket07682993", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + Object { + "Ref": "CloudFrontOriginAccessIdentity", + }, + ], + ], + }, + }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "S3Bucket07682993", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "S3Bucket07682993", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Principal": Object { + "CanonicalUser": Object { + "Fn::GetAtt": Array [ + "CloudFrontOriginAccessIdentity", + "S3CanonicalUserId", + ], + }, + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "S3Bucket07682993", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "S3LoggingBucket800A2B27": 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", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap new file mode 100644 index 000000000..9cd7842cb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/cloudwatch-log-group.test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cw log group with default params 1`] = ` +Object { + "Resources": Object { + "testcwlogsdefault5C05821C": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; + +exports[`cw log group with log group name 1`] = ` +Object { + "Resources": Object { + "testcwlogsdefault5C05821C": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "LogGroupName": "lambda-log-group", + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap new file mode 100644 index 000000000..373201104 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/congnito-helper.test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test buildUserPool default params 1`] = ` +Object { + "Resources": Object { + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + }, +} +`; + +exports[`snapshot test buildUserPoolClient default params 1`] = ` +Object { + "Resources": Object { + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap new file mode 100644 index 000000000..838238a35 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/dynamo-table.test.js.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test TableProps default params 1`] = ` +Object { + "Resources": Object { + "testdynamodefaults72AF3E8C": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "id", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "id", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; + +exports[`snapshot test TableWithStream default params 1`] = ` +Object { + "Resources": Object { + "testdynamostreamdefaultsFD08DF32": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "id", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "id", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + "StreamSpecification": Object { + "StreamViewType": "NEW_AND_OLD_IMAGES", + }, + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap new file mode 100644 index 000000000..958551ade --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/elasticsearch-helper.test.js.snap @@ -0,0 +1,326 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test buildElasticSearch default params 1`] = ` +Object { + "Resources": Object { + "CognitoAuthorizedRole14E74FE0": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": Object { + "ForAnyValue:StringLike": Object { + "cognito-identity.amazonaws.com:amr": "authenticated", + }, + "StringEquals": Object { + "cognito-identity.amazonaws.com:aud": Object { + "Ref": "CognitoIdentityPool", + }, + }, + }, + "Effect": "Allow", + "Principal": Object { + "Federated": "cognito-identity.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoAccessPolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CognitoIdentityPool": Object { + "Properties": Object { + "AllowUnauthenticatedIdentities": false, + "CognitoIdentityProviders": Array [ + Object { + "ClientId": Object { + "Ref": "CognitoUserPoolClient5AB59AE4", + }, + "ProviderName": Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "ProviderName", + ], + }, + "ServerSideTokenCheck": true, + }, + ], + }, + "Type": "AWS::Cognito::IdentityPool", + }, + "CognitoKibanaConfigureRole62CCE76A": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "es.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "CognitoKibanaConfigureRolePolicy76F46A5E": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DeleteUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:AdminInitiateAuth", + "cognito-idp:AdminUserGlobalSignOut", + "cognito-idp:ListUserPoolClients", + "cognito-identity:DescribeIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-identity:GetIdentityPoolRoles", + "es:UpdateElasticsearchDomainConfig", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:cognito-identity:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":identitypool/", + Object { + "Ref": "CognitoIdentityPool", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain", + ], + ], + }, + ], + }, + Object { + "Action": "iam:PassRole", + "Condition": Object { + "StringLike": Object { + "iam:PassedToService": "cognito-identity.amazonaws.com", + }, + }, + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CognitoKibanaConfigureRolePolicy76F46A5E", + "Roles": Array [ + Object { + "Ref": "CognitoKibanaConfigureRole62CCE76A", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "ClientName": "test", + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + "ElasticsearchDomain": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W28", + "reason": "The ES Domain is passed dynamically as as parameter and explicitly specified to ensure that IAM policies are configured to lockdown access to this specific ES instance only", + }, + ], + }, + }, + "Properties": Object { + "AccessPolicies": Object { + "Statement": Array [ + Object { + "Action": "es:ESHttp*", + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:es:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":domain/test-domain/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "CognitoOptions": Object { + "Enabled": true, + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "RoleArn": Object { + "Fn::GetAtt": Array [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn", + ], + }, + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "DomainName": "test-domain", + "EBSOptions": Object { + "EBSEnabled": true, + "VolumeSize": 10, + }, + "ElasticsearchClusterConfig": Object { + "DedicatedMasterCount": 3, + "DedicatedMasterEnabled": true, + "InstanceCount": 3, + "ZoneAwarenessConfig": Object { + "AvailabilityZoneCount": 3, + }, + "ZoneAwarenessEnabled": true, + }, + "ElasticsearchVersion": "6.3", + "EncryptionAtRestOptions": Object { + "Enabled": true, + }, + "NodeToNodeEncryptionOptions": Object { + "Enabled": true, + }, + "SnapshotOptions": Object { + "AutomatedSnapshotStartHour": 1, + }, + }, + "Type": "AWS::Elasticsearch::Domain", + }, + "IdentityPoolRoleMapping": Object { + "Properties": Object { + "IdentityPoolId": Object { + "Ref": "CognitoIdentityPool", + }, + "Roles": Object { + "authenticated": Object { + "Fn::GetAtt": Array [ + "CognitoAuthorizedRole14E74FE0", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Cognito::IdentityPoolRoleAttachment", + }, + "UserPoolDomain": Object { + "DependsOn": Array [ + "CognitoUserPool53E37E69", + ], + "Properties": Object { + "Domain": "test-domain", + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolDomain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap new file mode 100644 index 000000000..fff00aaeb --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/events-rule.test.js.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test EventsRuleProps default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "EventsD32975C2": Object { + "Properties": Object { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": Array [ + Object { + "Arn": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Id": "Target0", + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap new file mode 100644 index 000000000..749c4ad7d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/iot-rule.test.js.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test TopicRuleProps default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "IotTopic": Object { + "Properties": Object { + "TopicRulePayload": Object { + "Actions": Array [ + Object { + "Lambda": Object { + "FunctionArn": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + }, + }, + ], + "RuleDisabled": false, + "Sql": "SELECT * FROM 'topic/#'", + }, + }, + "Type": "AWS::IoT::TopicRule", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleC555A460", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRoleC555A460", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRoleC555A460": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap new file mode 100644 index 000000000..bc64ef78a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics-helper.test.js.snap @@ -0,0 +1,112 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test default functionality 1`] = ` +Object { + "Resources": Object { + "KinesisAnalytics": Object { + "DependsOn": Array [ + "KinesisAnalyticsPolicy88FFA7CD", + ], + "Properties": Object { + "Inputs": Array [ + Object { + "InputSchema": Object { + "RecordColumns": Array [ + Object { + "Mapping": "$.ticker_symbol", + "Name": "ticker_symbol", + "SqlType": "VARCHAR(4)", + }, + Object { + "Mapping": "$.sector", + "Name": "sector", + "SqlType": "VARCHAR(16)", + }, + Object { + "Mapping": "$.change", + "Name": "change", + "SqlType": "REAL", + }, + Object { + "Mapping": "$.price", + "Name": "price", + "SqlType": "REAL", + }, + ], + "RecordEncoding": "UTF-8", + "RecordFormat": Object { + "RecordFormatType": "JSON", + }, + }, + "KinesisFirehoseInput": Object { + "ResourceARN": Object { + "Fn::GetAtt": Array [ + "KinesisFirehose", + "Arn", + ], + }, + "RoleARN": Object { + "Fn::GetAtt": Array [ + "KinesisAnalyticsRoleCBFE2DD3", + "Arn", + ], + }, + }, + "NamePrefix": "SOURCE_SQL_STREAM", + }, + ], + }, + "Type": "AWS::KinesisAnalytics::Application", + }, + "KinesisAnalyticsPolicy88FFA7CD": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "firehose:DescribeDeliveryStream", + "firehose:Get*", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "KinesisFirehose", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "KinesisAnalyticsPolicy88FFA7CD", + "Roles": Array [ + Object { + "Ref": "KinesisAnalyticsRoleCBFE2DD3", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "KinesisAnalyticsRoleCBFE2DD3": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "kinesisanalytics.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "KinesisFirehose": Object { + "Type": "AWS::KinesisFirehose::DeliveryStream", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap new file mode 100644 index 000000000..a34b3b7b2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-analytics.test.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test kinesisanalytics default params 1`] = ` +Object { + "Resources": Object { + "KinesisAnalytics": Object { + "Properties": Object { + "Inputs": Array [], + }, + "Type": "AWS::KinesisAnalytics::Application", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap new file mode 100644 index 000000000..bc3b0eeab --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-firehose-s3-defaults.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test kinesisfirehose default params 1`] = ` +Object { + "Resources": Object { + "KinesisFirehose": Object { + "Properties": Object { + "ExtendedS3DestinationConfiguration": Object { + "BucketARN": "bucket_arn", + "BufferingHints": Object { + "IntervalInSeconds": 300, + "SizeInMBs": 5, + }, + "CloudWatchLoggingOptions": Object { + "Enabled": true, + "LogGroupName": "log_group", + "LogStreamName": "log_stream", + }, + "CompressionFormat": "GZIP", + "RoleARN": "role_arn", + }, + }, + "Type": "AWS::KinesisFirehose::DeliveryStream", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap new file mode 100644 index 000000000..16e052c13 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-defaults.test.js.snap @@ -0,0 +1,76 @@ +// 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": Object { + "Fn::GetAtt": Array [ + "KinesisStreamKey72E22A02", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Kinesis::Stream", + }, + "KinesisStreamKey72E22A02": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Description": "Created by KinesisStream", + "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/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap new file mode 100644 index 000000000..add414770 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kinesis-streams-helper.test.js.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +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", + }, + "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": Object { + "Fn::GetAtt": Array [ + "KinesisStreamKey72E22A02", + "Arn", + ], + }, + }, + }, + "Type": "AWS::Kinesis::Stream", + }, + "KinesisStreamKey72E22A02": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "Description": "Created by KinesisStream", + "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/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap new file mode 100644 index 000000000..ce4625cb1 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/kms-helper.test.js.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test minimal deployment with no 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", + }, + }, +} +`; + +exports[`Test minimal deployment with no properties 2`] = ` +Object { + "Resources": Object { + "EncryptionKey1B843E66": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "EnableKeyRotation": false, + "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/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap new file mode 100644 index 000000000..b05adba85 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/lambda-func.test.js.snap @@ -0,0 +1,142 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot test LambdaFunction default params 1`] = ` +Object { + "Parameters": Object { + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cArtifactHash00A70A91": Object { + "Description": "Artifact hash for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC": Object { + "Description": "S3 bucket for asset \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872": Object { + "Description": "S3 key for asset version \\"42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198c\\"", + "Type": "String", + }, + }, + "Resources": Object { + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3Bucket1F467BCC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters42a35bbf0dec9ef0ac5b0dde87e71a1b8929e8d2d178dd09ccfb2c928ec0198cS3VersionKey9E4F7872", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap new file mode 100644 index 000000000..4ae376bcc --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket-helper.test.js.snap @@ -0,0 +1,168 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`s3 bucket with default params 1`] = ` +Object { + "Resources": Object { + "S3Bucket07682993": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "S3LoggingBucket800A2B27", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "S3LoggingBucket800A2B27": 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", + }, + }, +} +`; + +exports[`s3 bucket with default params and bucket names 1`] = ` +Object { + "Resources": Object { + "S3Bucket07682993": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "BucketName": "my-bucket", + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "S3LoggingBucket800A2B27", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "S3LoggingBucket800A2B27": 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", + }, + }, +} +`; + +exports[`s3 bucket with existingBucketObj 1`] = ` +Object { + "Resources": Object { + "mybucket15D133BF": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap new file mode 100644 index 000000000..7d35947e6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/s3-bucket.test.js.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`s3 bucket with default params 1`] = ` +Object { + "Resources": Object { + "tests3defaults80430774": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "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", + }, + }, +} +`; diff --git a/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap new file mode 100644 index 000000000..de1aafec9 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sns-helper.test.js.snap @@ -0,0 +1,204 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +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", + }, + "SnsTopic2C1570A4": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "EncryptionKey1B843E66", + }, + "TopicName": "custom-topic", + }, + "Type": "AWS::SNS::Topic", + }, + }, +} +`; + +exports[`Test deployment w/ imported encryption key 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", + }, + "SnsTopic2C1570A4": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "EncryptionKey1B843E66", + }, + "TopicName": "custom-topic", + }, + "Type": "AWS::SNS::Topic", + }, + }, +} +`; + +exports[`Test deployment with no 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", + }, + "SnsTopic2C1570A4": Object { + "Properties": Object { + "KmsMasterKeyId": Object { + "Ref": "EncryptionKey1B843E66", + }, + }, + "Type": "AWS::SNS::Topic", + }, + }, +} +`; 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 new file mode 100644 index 000000000..21679a006 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/__snapshots__/sqs-helper.test.js.snap @@ -0,0 +1,283 @@ +// 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/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts new file mode 100644 index 000000000..a0ac15c63 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/apigateway-helper.test.ts @@ -0,0 +1,217 @@ +/** + * 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, ResourcePart } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as api from '@aws-cdk/aws-apigateway'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; + +function deployRegionalApiGateway(stack: Stack) { + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + return defaults.RegionalLambdaRestApi(stack, fn); +} + +test('snapshot test RegionalApiGateway default params', () => { + const stack = new Stack(); + deployRegionalApiGateway(stack); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('Test override for RegionalApiGateway', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + defaults.RegionalLambdaRestApi(stack, fn, { + handler: fn, + description: 'Hello World' + }); + + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', { + Type: "AWS::ApiGateway::RestApi", + Properties: { + Description: "Hello World", + EndpointConfiguration: { + Types: [ + "REGIONAL" + ] + }, + Name: "RestApi" + } + }, ResourcePart.CompleteDefinition); +}); + +test('Test override for GlobalApiGateway', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + defaults.GlobalLambdaRestApi(stack, fn, { + handler: fn, + restApiName: "HelloWorld" + }); + + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', { + Type: "AWS::ApiGateway::RestApi", + Properties: { + EndpointConfiguration: { + Types: [ + "EDGE" + ] + }, + Name: "HelloWorld" + } + }, ResourcePart.CompleteDefinition); +}); + +test('Test ApiGateway::Account resource for RegionalApiGateway', () => { + const stack = new Stack(); + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + defaults.RegionalLambdaRestApi(stack, fn); + + expect(stack).toHaveResource('AWS::ApiGateway::Account', { + CloudWatchRoleArn: { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }); +}); + +test('Test ApiGateway::Account resource for GlobalApiGateway', () => { + const stack = new Stack(); + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + defaults.GlobalLambdaRestApi(stack, fn); + + expect(stack).toHaveResource('AWS::ApiGateway::Account', { + CloudWatchRoleArn: { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }); +}); + +test('Test default RestApi deployment w/o ApiGatewayProps', () => { + const stack = new Stack(); + setupRestApi(stack); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('Test default RestApi deployment w/ ApiGatewayProps', () => { + const stack = new Stack(); + setupRestApi(stack, { + restApiName: "customRestApi" + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource('AWS::ApiGateway::RestApi', { + Name: "customRestApi" + }); +}); + +function setupRestApi(stack: Stack, apiProps?: any): void { + const restApi = defaults.GlobalRestApi(stack, apiProps); + // Setup the API Gateway resource + const apiGatewayResource = restApi.root.addResource('api-gateway-resource'); + // Setup the API Gateway Integration + const apiGatewayIntegration = new api.AwsIntegration({ + service: "sqs", + integrationHttpMethod: "POST", + options: { + passthroughBehavior: api.PassthroughBehavior.NEVER, + requestParameters: { + "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" + }, + requestTemplates: { + "application/x-www-form-urlencoded": "Action=SendMessage&MessageBody=$util.urlEncode(\"$input.body\")&MessageAttribute.1.Name=queryParam1&MessageAttribute.1.Value.StringValue=$input.params(\"query_param_1\")&MessageAttribute.1.Value.DataType=String" + }, + integrationResponses: [ + { + statusCode: "200", + responseTemplates: { + "text/html": "Success" + } + }, + { + statusCode: "500", + responseTemplates: { + "text/html": "Error" + }, + selectionPattern: "500" + } + ] + }, + path: '12345678' + "/" + 'thisqueuequeueName' + }); + // Setup the API Gateway method(s) + apiGatewayResource.addMethod('POST', apiGatewayIntegration, { + requestParameters: { + "method.request.querystring.query_param_1": true + }, + methodResponses: [ + { + statusCode: "200", + responseParameters: { + "method.response.header.Content-Type": true + } + }, + { + statusCode: "500", + responseParameters: { + "method.response.header.Content-Type": true + }, + } + ] + }); +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts new file mode 100644 index 000000000..f1b0f2e3a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-api-gateway-helper.test.ts @@ -0,0 +1,284 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import * as api from '@aws-cdk/aws-apigateway'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '../index'; +import * as s3 from '@aws-cdk/aws-s3'; +import { CloudFrontDistributionForApiGateway } from '../lib/cloudfront-distribution-helper'; +import '@aws-cdk/assert/jest'; +import { OriginProtocolPolicy } from '@aws-cdk/aws-cloudfront'; + +test('cloudfront distribution for ApiGateway with default params', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const func = new lambda.Function(stack, 'LambdaFunction', lambdaFunctionProps); + const _api = new api.LambdaRestApi(stack, 'RestApi', { + handler: func + }); + CloudFrontDistributionForApiGateway(stack, _api); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test cloudfront for Api Gateway with user provided logging bucket', () => { + const stack = new Stack(); + + const loggingBucket: s3.Bucket = new s3.Bucket(stack, 'MyCloudfrontLoggingBucket', defaults.DefaultS3Props()); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler' + }; + + const cfdProps = { + loggingConfig: { + bucket: loggingBucket + } + }; + + const func = defaults.deployLambdaFunction(stack, inProps); + + const _api = new api.LambdaRestApi(stack, 'RestApi1', { + handler: func + }); + + CloudFrontDistributionForApiGateway(stack, _api, cfdProps); + expect(stack).toHaveResourceLike("AWS::CloudFront::Distribution", { + DistributionConfig: { + DefaultCacheBehavior: { + AllowedMethods: [ + "GET", + "HEAD" + ], + CachedMethods: [ + "GET", + "HEAD" + ], + Compress: true, + ForwardedValues: { + Cookies: { + Forward: "none" + }, + QueryString: false + }, + TargetOriginId: "origin1", + ViewerProtocolPolicy: "redirect-to-https" + }, + DefaultRootObject: "index.html", + Enabled: true, + HttpVersion: "http2", + IPV6Enabled: true, + Logging: { + Bucket: { + "Fn::GetAtt": [ + "MyCloudfrontLoggingBucket9AA652E8", + "RegionalDomainName" + ] + }, + IncludeCookies: false + }, + Origins: [ + { + CustomOriginConfig: { + HTTPPort: 80, + HTTPSPort: 443, + OriginKeepaliveTimeout: 5, + OriginProtocolPolicy: "https-only", + OriginReadTimeout: 30, + OriginSSLProtocols: [ + "TLSv1.2" + ] + }, + DomainName: { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "://", + { + "Fn::Join": [ + "", + [ + "https://", + { + Ref: "RestApi1480AC499" + }, + ".execute-api.", + { + Ref: "AWS::Region" + }, + ".", + { + Ref: "AWS::URLSuffix" + }, + "/", + { + Ref: "RestApi1DeploymentStageprod4FFC9BB4" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + } + ] + }, + Id: "origin1" + } + ], + PriceClass: "PriceClass_100", + ViewerCertificate: { + CloudFrontDefaultCertificate: true + } + } + }); +}); + +test('test cloudfront for Api Gateway override properties', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler' + }; + + const func = defaults.deployLambdaFunction(stack, inProps); + + const _api = new api.LambdaRestApi(stack, 'RestApi1', { + handler: func + }); + + const props: cloudfront.CloudFrontWebDistributionProps = { + originConfigs: [ { + customOriginSource: { + domainName: _api.url, + originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY + }, + behaviors: [ { + isDefaultBehavior: true, + allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL, + cachedMethods: cloudfront.CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS + } ] + } ] + }; + + CloudFrontDistributionForApiGateway(stack, _api, props); + + expect(stack).toHaveResourceLike("AWS::CloudFront::Distribution", { + DistributionConfig: { + DefaultCacheBehavior: { + AllowedMethods: [ + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT" + ], + CachedMethods: [ + "GET", + "HEAD", + "OPTIONS" + ], + Compress: true, + ForwardedValues: { + Cookies: { + Forward: "none" + }, + QueryString: false + }, + TargetOriginId: "origin1", + ViewerProtocolPolicy: "redirect-to-https" + }, + DefaultRootObject: "index.html", + Enabled: true, + HttpVersion: "http2", + IPV6Enabled: true, + Logging: { + Bucket: { + "Fn::GetAtt": [ + "CloudfrontLoggingBucket3C3EFAA7", + "RegionalDomainName" + ] + }, + IncludeCookies: false + }, + Origins: [ + { + CustomOriginConfig: { + HTTPPort: 80, + HTTPSPort: 443, + OriginKeepaliveTimeout: 5, + OriginProtocolPolicy: "http-only", + OriginReadTimeout: 30, + OriginSSLProtocols: [ + "TLSv1.2" + ] + }, + DomainName: { + "Fn::Join": [ + "", + [ + "https://", + { + Ref: "RestApi1480AC499" + }, + ".execute-api.", + { + Ref: "AWS::Region" + }, + ".", + { + Ref: "AWS::URLSuffix" + }, + "/", + { + Ref: "RestApi1DeploymentStageprod4FFC9BB4" + }, + "/" + ] + ] + }, + Id: "origin1" + } + ], + PriceClass: "PriceClass_100", + ViewerCertificate: { + CloudFrontDefaultCertificate: true + } + } + }); + +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts new file mode 100644 index 000000000..8f08f8dee --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/cloudfront-distribution-s3-helper.test.ts @@ -0,0 +1,267 @@ +/** + * 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, ResourcePart } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/core'; +import * as cloudfront from '@aws-cdk/aws-cloudfront'; +import { CloudFrontDistributionForS3 } from '../lib/cloudfront-distribution-helper'; +import { buildS3Bucket } from '../lib/s3-bucket-helper'; +import '@aws-cdk/assert/jest'; + +test('cloudfront distribution with default params', () => { + const stack = new Stack(); + const sourceBucket = buildS3Bucket(stack, { + deployBucket: true + }); + CloudFrontDistributionForS3(stack, sourceBucket); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check bucket policy metadata', () => { + const stack = new Stack(); + const sourceBucket = buildS3Bucket(stack, { + deployBucket: true + }); + CloudFrontDistributionForS3(stack, sourceBucket); + expect(stack).toHaveResource('AWS::S3::BucketPolicy', { + Metadata: { + cfn_nag: { + rules_to_suppress: [ + { + id: "F16", + reason: "Public website bucket policy requires a wildcard principal" + } + ] + } + } + }, ResourcePart.CompleteDefinition); +}); + +test('check bucket metadata', () => { + const stack = new Stack(); + const sourceBucket = buildS3Bucket(stack, { + deployBucket: true + }); + CloudFrontDistributionForS3(stack, sourceBucket); + expect(stack).toHaveResource('AWS::S3::Bucket', { + Metadata: { + cfn_nag: { + rules_to_suppress: [ + { + id: "W35", + reason: "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + }, + { + id: "W51", + reason: "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, ResourcePart.CompleteDefinition); +}); + +test('test cloudfront check bucket policy', () => { + const stack = new Stack(); + const sourceBucket = buildS3Bucket(stack, { + deployBucket: true + }); + CloudFrontDistributionForS3(stack, sourceBucket); + + expect(stack).toHaveResourceLike("AWS::S3::BucketPolicy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + Effect: "Allow", + Principal: { + AWS: { + "Fn::Join": [ + "", + [ + "arn:", + { + Ref: "AWS::Partition" + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + { + Ref: "CloudFrontOriginAccessIdentity" + } + ] + ] + } + }, + Resource: [ + { + "Fn::GetAtt": [ + "S3Bucket07682993", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "S3Bucket07682993", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + Action: "s3:GetObject", + Effect: "Allow", + Principal: { + CanonicalUser: { + "Fn::GetAtt": [ + "CloudFrontOriginAccessIdentity", + "S3CanonicalUserId" + ] + } + }, + Resource: { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "S3Bucket07682993", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + Version: "2012-10-17" + } + }); +}); + +test('test cloudfront override properties', () => { + const stack = new Stack(); + const sourceBucket = buildS3Bucket(stack, { + deployBucket: true + }); + // Create CloudFront Origin Access Identity User + const cfnOrigAccessId = new cloudfront.CfnCloudFrontOriginAccessIdentity(stack, 'CloudFrontOriginAccessIdentity1', { + cloudFrontOriginAccessIdentityConfig: { + comment: 'Access S3 bucket content only through CloudFront' + } + }); + + const oaiImported = cloudfront.OriginAccessIdentity.fromOriginAccessIdentityName( + stack, + 'OAIImported1', + cfnOrigAccessId.ref + ); + + const props: cloudfront.CloudFrontWebDistributionProps = { + originConfigs: [ { + s3OriginSource: { + s3BucketSource: sourceBucket, + originAccessIdentity: oaiImported + }, + behaviors: [ { + isDefaultBehavior: true, + allowedMethods: cloudfront.CloudFrontAllowedMethods.ALL, + cachedMethods: cloudfront.CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS + } ] + } ] + }; + + CloudFrontDistributionForS3(stack, sourceBucket, props); + + expect(stack).toHaveResourceLike("AWS::CloudFront::Distribution", { + DistributionConfig: { + DefaultCacheBehavior: { + AllowedMethods: [ + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT" + ], + CachedMethods: [ + "GET", + "HEAD", + "OPTIONS" + ], + Compress: true, + ForwardedValues: { + Cookies: { + Forward: "none" + }, + QueryString: false + }, + TargetOriginId: "origin1", + ViewerProtocolPolicy: "redirect-to-https" + }, + DefaultRootObject: "index.html", + Enabled: true, + HttpVersion: "http2", + IPV6Enabled: true, + Logging: { + Bucket: { + "Fn::GetAtt": [ + "CloudfrontLoggingBucket3C3EFAA7", + "RegionalDomainName" + ] + }, + IncludeCookies: false + }, + Origins: [ + { + DomainName: { + "Fn::GetAtt": [ + "S3Bucket07682993", + "RegionalDomainName" + ] + }, + Id: "origin1", + S3OriginConfig: { + OriginAccessIdentity: { + "Fn::Join": [ + "", + [ + "origin-access-identity/cloudfront/", + { + Ref: "CloudFrontOriginAccessIdentity1" + } + ] + ] + } + } + } + ], + PriceClass: "PriceClass_100", + ViewerCertificate: { + CloudFrontDefaultCertificate: true + } + } + }); + }); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts new file mode 100644 index 000000000..de6bf6a97 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/cloudwatch-log-group.test.ts @@ -0,0 +1,30 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; +import * as logs from '@aws-cdk/aws-logs'; + +test('cw log group with default params', () => { + const stack = new Stack(); + new logs.LogGroup(stack, 'test-cw-logs-default', defaults.DefaultLogGroupProps()); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('cw log group with log group name', () => { + const stack = new Stack(); + new logs.LogGroup(stack, 'test-cw-logs-default', defaults.DefaultLogGroupProps('lambda-log-group')); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts new file mode 100644 index 000000000..d6d9e45b9 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/congnito-helper.test.ts @@ -0,0 +1,198 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as cognito from '@aws-cdk/aws-cognito'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; + +test('snapshot test buildUserPool default params', () => { + const stack = new Stack(); + defaults.buildUserPool(stack); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('snapshot test buildUserPoolClient default params', () => { + const stack = new Stack(); + const userpool = defaults.buildUserPool(stack); + defaults.buildUserPoolClient(stack, userpool); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('Test override for buildUserPool', () => { + const stack = new Stack(); + + const userpoolProps: cognito.UserPoolProps = { + userPoolName: 'test', + signInType: cognito.SignInType.EMAIL_OR_PHONE + }; + + defaults.buildUserPool(stack, userpoolProps); + + expect(stack).toHaveResource('AWS::Cognito::UserPool', { + LambdaConfig: {}, + UsernameAttributes: [ + "email", + "phone_number" + ], + UserPoolAddOns: { + AdvancedSecurityMode: "ENFORCED" + }, + UserPoolName: "test" + }); +}); + +test('Test override for buildUserPoolClient', () => { + const stack = new Stack(); + + const userpool = defaults.buildUserPool(stack); + + const userpoolclientProps: cognito.UserPoolClientProps = { + userPoolClientName: 'test', + userPool: userpool + }; + + defaults.buildUserPoolClient(stack, userpool, userpoolclientProps); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + }, + ClientName: "test" + }); +}); + +test('Test override for buildIdentityPool', () => { + const stack = new Stack(); + + const userpool = defaults.buildUserPool(stack); + const userpoolclient = defaults.buildUserPoolClient(stack, userpool, { + userPoolClientName: 'test', + userPool: userpool + }); + defaults.buildIdentityPool(stack, userpool, userpoolclient, { + allowUnauthenticatedIdentities: true + }); + + expect(stack).toHaveResource('AWS::Cognito::IdentityPool', { + AllowUnauthenticatedIdentities: true, + CognitoIdentityProviders: [ + { + ClientId: { + Ref: "CognitoUserPoolClient5AB59AE4" + }, + ProviderName: { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "ProviderName" + ] + }, + ServerSideTokenCheck: true + } + ] + }); +}); + +test('Test setupCognitoForElasticSearch', () => { + const stack = new Stack(); + + const userpool = defaults.buildUserPool(stack); + const userpoolclient = defaults.buildUserPoolClient(stack, userpool, { + userPoolClientName: 'test', + userPool: userpool + }); + const identitypool = defaults.buildIdentityPool(stack, userpool, userpoolclient); + + defaults.setupCognitoForElasticSearch(stack, 'test-domain', { + userpool, + userpoolclient, + identitypool + }); + + expect(stack).toHaveResource('AWS::Cognito::UserPoolDomain', { + Domain: "test-domain" + }); + + expect(stack).toHaveResource('AWS::Cognito::IdentityPoolRoleAttachment', { + IdentityPoolId: { + Ref: "CognitoIdentityPool" + }, + Roles: { + authenticated: { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + } + } + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: "sts:AssumeRoleWithWebIdentity", + Condition: { + "StringEquals": { + "cognito-identity.amazonaws.com:aud": { + Ref: "CognitoIdentityPool" + } + }, + "ForAnyValue:StringLike": { + "cognito-identity.amazonaws.com:amr": "authenticated" + } + }, + Effect: "Allow", + Principal: { + Federated: "cognito-identity.amazonaws.com" + } + } + ], + Version: "2012-10-17" + }, + Policies: [ + { + PolicyDocument: { + Statement: [ + { + Action: "es:ESHttp*", + Effect: "Allow", + Resource: { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":domain/test-domain/*" + ] + ] + } + } + ], + Version: "2012-10-17" + }, + PolicyName: "CognitoAccessPolicy" + } + ] + }); + +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts new file mode 100644 index 000000000..8d2f9682e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/dynamo-table.test.ts @@ -0,0 +1,158 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as dynamodb from '@aws-cdk/aws-dynamodb'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('snapshot test TableProps default params', () => { + const stack = new Stack(); + new dynamodb.Table(stack, 'test-dynamo-defaults', defaults.DefaultTableProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('snapshot test TableWithStream default params', () => { + const stack = new Stack(); + new dynamodb.Table(stack, 'test-dynamo-stream-defaults', defaults.DefaultTableWithStreamProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test TableProps change billing mode', () => { + const stack = new Stack(); + + const defaultProps: dynamodb.TableProps = defaults.DefaultTableProps; + + const inProps: dynamodb.TableProps = { + billingMode: dynamodb.BillingMode.PROVISIONED, + readCapacity: 3, + writeCapacity: 3, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + } + }; + + const outProps = overrideProps(defaultProps, inProps); + new dynamodb.Table(stack, 'test-dynamo-override', outProps); + + expect(stack).toHaveResource("AWS::DynamoDB::Table", { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + } + ], + ProvisionedThroughput: { + ReadCapacityUnits: 3, + WriteCapacityUnits: 3 + }, + SSESpecification: { + SSEEnabled: true + } + }); +}); + +test('test TableProps override add sort key', () => { + const stack = new Stack(); + + const defaultProps: dynamodb.TableProps = defaults.DefaultTableProps; + + const inProps: dynamodb.TableProps = { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + }, + sortKey: { + name: 'sort_key', + type: dynamodb.AttributeType.STRING + } + }; + + const outProps = overrideProps(defaultProps, inProps); + new dynamodb.Table(stack, 'test-dynamo-override', outProps); + + expect(stack).toHaveResource("AWS::DynamoDB::Table", { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + }, + { + AttributeName: "sort_key", + KeyType: "RANGE" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + }, + { + AttributeName: "sort_key", + AttributeType: "S" + } + ], + BillingMode: "PAY_PER_REQUEST", + SSESpecification: { + SSEEnabled: true + } + }); +}); + +test('test TableWithStreamProps override stream view type', () => { + const stack = new Stack(); + + const defaultProps: dynamodb.TableProps = defaults.DefaultTableWithStreamProps; + + const inProps: dynamodb.TableProps = { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING + }, + stream: dynamodb.StreamViewType.NEW_IMAGE + }; + + const outProps = overrideProps(defaultProps, inProps); + new dynamodb.Table(stack, 'test-dynamo-override', outProps); + + expect(stack).toHaveResource("AWS::DynamoDB::Table", { + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH" + } + ], + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S" + } + ], + BillingMode: "PAY_PER_REQUEST", + SSESpecification: { + SSEEnabled: true + }, + StreamSpecification: { + StreamViewType: "NEW_IMAGE" + } + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts new file mode 100644 index 000000000..116ddad01 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/elasticsearch-helper.test.ts @@ -0,0 +1,315 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as elasticsearch from '@aws-cdk/aws-elasticsearch'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; + +function deployES(stack: Stack, domainName: string, cfnDomainProps?: elasticsearch.CfnDomainProps, + lambdaRoleARN?: string): elasticsearch.CfnDomain { + const userpool = defaults.buildUserPool(stack); + const userpoolclient = defaults.buildUserPoolClient(stack, userpool, { + userPoolClientName: 'test', + userPool: userpool + }); + const identitypool = defaults.buildIdentityPool(stack, userpool, userpoolclient); + + const cognitoAuthorizedRole = defaults.setupCognitoForElasticSearch(stack, 'test-domain', { + userpool, + userpoolclient, + identitypool + }); + + if (lambdaRoleARN) { + return defaults.buildElasticSearch(stack, domainName, { + userpool, + identitypool, + cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn, + serviceRoleARN: lambdaRoleARN + }, cfnDomainProps); + } else { + return defaults.buildElasticSearch(stack, domainName, { + userpool, + identitypool, + cognitoAuthorizedRoleARN: cognitoAuthorizedRole.roleArn + }, cfnDomainProps); + } +} + +test('snapshot test buildElasticSearch default params', () => { + const stack = new Stack(); + deployES(stack, 'test-domain'); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('Test override SnapshotOptions for buildElasticSearch', () => { + const stack = new Stack(); + deployES(stack, 'test-domain', { + snapshotOptions: { + automatedSnapshotStartHour: 5 + } + }); + + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + AccessPolicies: { + Statement: [ + { + Action: "es:ESHttp*", + Effect: "Allow", + Principal: { + AWS: { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + } + }, + Resource: { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":domain/test-domain/*" + ] + ] + } + } + ], + Version: "2012-10-17" + }, + CognitoOptions: { + Enabled: true, + IdentityPoolId: { + Ref: "CognitoIdentityPool" + }, + RoleArn: { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + }, + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + } + }, + DomainName: "test-domain", + EBSOptions: { + EBSEnabled: true, + VolumeSize: 10 + }, + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessConfig: { + AvailabilityZoneCount: 3 + }, + ZoneAwarenessEnabled: true + }, + ElasticsearchVersion: "6.3", + EncryptionAtRestOptions: { + Enabled: true + }, + NodeToNodeEncryptionOptions: { + Enabled: true + }, + SnapshotOptions: { + AutomatedSnapshotStartHour: 5 + } + }); +}); + +test('Test override ES version for buildElasticSearch', () => { + const stack = new Stack(); + deployES(stack, 'test-domain', { + elasticsearchVersion: '7.0' + }); + + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + AccessPolicies: { + Statement: [ + { + Action: "es:ESHttp*", + Effect: "Allow", + Principal: { + AWS: { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + } + }, + Resource: { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":domain/test-domain/*" + ] + ] + } + } + ], + Version: "2012-10-17" + }, + CognitoOptions: { + Enabled: true, + IdentityPoolId: { + Ref: "CognitoIdentityPool" + }, + RoleArn: { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + }, + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + } + }, + DomainName: "test-domain", + EBSOptions: { + EBSEnabled: true, + VolumeSize: 10 + }, + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessConfig: { + AvailabilityZoneCount: 3 + }, + ZoneAwarenessEnabled: true + }, + ElasticsearchVersion: "7.0", + EncryptionAtRestOptions: { + Enabled: true + }, + NodeToNodeEncryptionOptions: { + Enabled: true + }, + SnapshotOptions: { + AutomatedSnapshotStartHour: 1 + } + }); + +}); + +test('Test ES with lambdaRoleARN', () => { + const stack = new Stack(); + deployES(stack, 'test-domain', {}, 'arn:aws:us-east-1:mylambdaRoleARN'); + + expect(stack).toHaveResource('AWS::Elasticsearch::Domain', { + AccessPolicies: { + Statement: [ + { + Action: "es:ESHttp*", + Effect: "Allow", + Principal: { + AWS: [ + { + "Fn::GetAtt": [ + "CognitoAuthorizedRole14E74FE0", + "Arn" + ] + }, + "arn:aws:us-east-1:mylambdaRoleARN" + ] + }, + Resource: { + "Fn::Join": [ + "", + [ + "arn:aws:es:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":domain/test-domain/*" + ] + ] + } + } + ], + Version: "2012-10-17" + }, + CognitoOptions: { + Enabled: true, + IdentityPoolId: { + Ref: "CognitoIdentityPool" + }, + RoleArn: { + "Fn::GetAtt": [ + "CognitoKibanaConfigureRole62CCE76A", + "Arn" + ] + }, + UserPoolId: { + Ref: "CognitoUserPool53E37E69" + } + }, + DomainName: "test-domain", + EBSOptions: { + EBSEnabled: true, + VolumeSize: 10 + }, + ElasticsearchClusterConfig: { + DedicatedMasterCount: 3, + DedicatedMasterEnabled: true, + InstanceCount: 3, + ZoneAwarenessConfig: { + AvailabilityZoneCount: 3 + }, + ZoneAwarenessEnabled: true + }, + ElasticsearchVersion: "6.3", + EncryptionAtRestOptions: { + Enabled: true + }, + NodeToNodeEncryptionOptions: { + Enabled: true + }, + SnapshotOptions: { + AutomatedSnapshotStartHour: 1 + } + }); + +}); + +test('Count ES CW Alarms', () => { + const stack = new Stack(); + deployES(stack, 'test-domain'); + const cwList = defaults.buildElasticSearchCWAlarms(stack); + + expect(cwList.length).toEqual(9); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts new file mode 100644 index 000000000..fc0f4af53 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/events-rule.test.ts @@ -0,0 +1,124 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as events from '@aws-cdk/aws-events'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; +import { Schedule } from '@aws-cdk/aws-events'; +import { Duration } from '@aws-cdk/core'; +import { overrideProps } from '../lib/utils'; + +test('snapshot test EventsRuleProps default params', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + const lambdaFunc: events.IRuleTarget = { + bind: () => ({ + id: '', + arn: fn.functionArn + }) + }; + + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([lambdaFunc]); + const eventsRuleProps = overrideProps(defaultEventsRuleProps, { + schedule: Schedule.rate(Duration.minutes(5)) + }); + + new events.Rule(stack, 'Events', eventsRuleProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test EventsRuleProps override ruleName and description', () => { + const stack = new Stack(); + + const lambdaFunc: events.IRuleTarget = { + bind: () => ({ + id: '', + arn: 'ARN' + }) + }; + + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([lambdaFunc]); + const eventsRuleProps = overrideProps(defaultEventsRuleProps, { + ruleName: 'test', + description: 'hello world', + schedule: Schedule.rate(Duration.minutes(5)) + } as events.RuleProps); + + new events.Rule(stack, 'Events', eventsRuleProps); + + expect(stack).toHaveResource('AWS::Events::Rule', { + Description: "hello world", + Name: "test", + ScheduleExpression: "rate(5 minutes)", + State: "ENABLED", + Targets: [ + { + Arn: "ARN", + Id: "Target0" + } + ] + }); +}); + +test('test EventsRuleProps add more event targets', () => { + const stack = new Stack(); + + const lambdaFunc1: events.IRuleTarget = { + bind: () => ({ + id: '', + arn: 'ARN1' + }) + }; + + const defaultEventsRuleProps = defaults.DefaultEventsRuleProps([lambdaFunc1]); + + const lambdaFunc2: events.IRuleTarget = { + bind: () => ({ + id: '', + arn: 'ARN2' + }) + }; + + const eventsRuleProps = overrideProps(defaultEventsRuleProps, { + targets: [lambdaFunc2], + schedule: Schedule.rate(Duration.minutes(5)) + } as events.RuleProps, true); + + new events.Rule(stack, 'Events', eventsRuleProps); + + expect(stack).toHaveResource('AWS::Events::Rule', { + ScheduleExpression: "rate(5 minutes)", + State: "ENABLED", + Targets: [ + { + Arn: "ARN1", + Id: "Target0" + }, + { + Arn: "ARN2", + Id: "Target1" + } + ] + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts new file mode 100644 index 000000000..a697a0171 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/iot-rule.test.ts @@ -0,0 +1,118 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as iot from '@aws-cdk/aws-iot'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('snapshot test TopicRuleProps default params', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + const fn = new lambda.Function(stack, 'LambdaFunction', lambdaFunctionProps); + + const defaultIotTopicProps = defaults.DefaultCfnTopicRuleProps([{ + lambda: { + functionArn: fn.functionArn + } + }], "SELECT * FROM 'topic/#'"); + new iot.CfnTopicRule(stack, 'IotTopic', defaultIotTopicProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test TopicRuleProps override sql and description', () => { + const stack = new Stack(); + + const action1: iot.CfnTopicRule.ActionProperty = { + lambda: { + functionArn: 'xyz' + } + }; + + const defaultProps: iot.CfnTopicRuleProps = defaults.DefaultCfnTopicRuleProps([action1]); + + const inProps: iot.CfnTopicRuleProps = { + topicRulePayload: { + ruleDisabled: true, + description: "Processing of vehicle messages", + sql: "SELECT * FROM 'connectedcar/#'", + actions: [] + } + }; + + const outProps = overrideProps(defaultProps, inProps, true); + + new iot.CfnTopicRule(stack, 'IotTopic', outProps); + + expect(stack).toHaveResource('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Lambda: { + FunctionArn: "xyz" + } + } + ], + Description: "Processing of vehicle messages", + RuleDisabled: true, + Sql: "SELECT * FROM 'connectedcar/#'" + } + }); +}); + +test('test TopicRuleProps override actions', () => { + const stack = new Stack(); + + const defaultProps: iot.CfnTopicRuleProps = defaults.DefaultCfnTopicRuleProps([], ''); + + const action: iot.CfnTopicRule.ActionProperty = { + lambda: { + functionArn: 'abc' + } + }; + + const inProps: iot.CfnTopicRuleProps = { + topicRulePayload: { + ruleDisabled: true, + sql: '', + actions: [action] + } + }; + + const outProps = overrideProps(defaultProps, inProps); + + new iot.CfnTopicRule(stack, 'IotTopic', outProps); + + expect(stack).toHaveResource('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + Lambda: { + FunctionArn: "abc" + } + } + ], + RuleDisabled: true, + Sql: "" + } + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts new file mode 100644 index 000000000..9942a6439 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics-helper.test.ts @@ -0,0 +1,62 @@ +/** + * 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 kinesisFirehose from "@aws-cdk/aws-kinesisfirehose"; +import * as defaults from '../'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test default functionality +// -------------------------------------------------------------- +test('Test default functionality', () => { + // Setup the stack + const stack = new Stack(); + const firehose = new kinesisFirehose.CfnDeliveryStream(stack, 'KinesisFirehose'); + // Setup the Kinesis Analytics application + defaults.buildKinesisAnalyticsApp(stack, { + kinesisFirehose: firehose, + kinesisAnalyticsProps: { + inputs: [{ + inputSchema: { + recordColumns: [{ + name: 'ticker_symbol', + sqlType: 'VARCHAR(4)', + mapping: '$.ticker_symbol' + }, { + name: 'sector', + sqlType: 'VARCHAR(16)', + mapping: '$.sector' + }, { + name: 'change', + sqlType: 'REAL', + mapping: '$.change' + }, { + name: 'price', + sqlType: 'REAL', + mapping: '$.price' + }], + recordFormat: { + recordFormatType: 'JSON' + }, + recordEncoding: 'UTF-8' + }, + namePrefix: 'SOURCE_SQL_STREAM' + }] + } + }); + // Assertions + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts new file mode 100644 index 000000000..999a7f4c2 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-analytics.test.ts @@ -0,0 +1,66 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as kinesisanalytics from '@aws-cdk/aws-kinesisanalytics'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('snapshot test kinesisanalytics default params', () => { + const stack = new Stack(); + new kinesisanalytics.CfnApplication(stack, 'KinesisAnalytics', defaults.DefaultCfnApplicationProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test kinesisanalytics override inputProperty', () => { + const stack = new Stack(); + + const inputProperty: kinesisanalytics.CfnApplication.InputProperty = { + inputSchema: { + recordColumns: [{name: 'x', sqlType: 'y'}], + recordFormat: { recordFormatType: 'csv' } + }, + namePrefix: 'zzz' + }; + + const defaultProps: kinesisanalytics.CfnApplicationProps = defaults.DefaultCfnApplicationProps; + + const inProps: kinesisanalytics.CfnApplicationProps = { + inputs: [inputProperty] + }; + + const outProps = overrideProps(defaultProps, inProps); + + new kinesisanalytics.CfnApplication(stack, 'KinesisAnalytics', outProps); + + expect(stack).toHaveResource("AWS::KinesisAnalytics::Application", { + Inputs: [ + { + InputSchema: { + RecordColumns: [ + { + Name: "x", + SqlType: "y" + } + ], + RecordFormat: { + RecordFormatType: "csv" + } + }, + NamePrefix: "zzz" + } + ] + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts new file mode 100644 index 000000000..7c4f1ed59 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-firehose-s3-defaults.test.ts @@ -0,0 +1,62 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('snapshot test kinesisfirehose default params', () => { + const stack = new Stack(); + new kinesisfirehose.CfnDeliveryStream(stack, 'KinesisFirehose', + defaults.DefaultCfnDeliveryStreamProps('bucket_arn', 'role_arn', 'log_group', 'log_stream')); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test kinesisanalytics override buffer conditions', () => { + const stack = new Stack(); + + const inProps = { + extendedS3DestinationConfiguration: { + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 10 + }, + } + }; + + const defaultProps = defaults.DefaultCfnDeliveryStreamProps('bucket_arn', 'role_arn', 'log_group', 'log_stream'); + + const outProps = overrideProps(defaultProps, inProps); + + new kinesisfirehose.CfnDeliveryStream(stack, 'KinesisFirehose', outProps); + + expect(stack).toHaveResource("AWS::KinesisFirehose::DeliveryStream", { + ExtendedS3DestinationConfiguration: { + BucketARN: "bucket_arn", + BufferingHints: { + IntervalInSeconds: 600, + SizeInMBs: 10 + }, + CloudWatchLoggingOptions: { + Enabled: true, + LogGroupName: "log_group", + LogStreamName: "log_stream" + }, + CompressionFormat: "GZIP", + RoleARN: "role_arn" + } + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts new file mode 100644 index 000000000..a3862a026 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-defaults.test.ts @@ -0,0 +1,43 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as kinesis from '@aws-cdk/aws-kinesis'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('snapshot test kinesisstream default params', () => { + const stack = new Stack(); + new kinesis.Stream(stack, 'KinesisStream', defaults.DefaultStreamProps); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test kinesisstream override RetentionPeriodHours', () => { + const stack = new Stack(); + + const defaultProps = defaults.DefaultStreamProps; + + const inProps: kinesis.StreamProps = { + retentionPeriodHours: 48 + }; + + const outProps = overrideProps(defaultProps, inProps); + + new kinesis.Stream(stack, 'KinesisStream', outProps); + + expect(stack).toHaveResource("AWS::Kinesis::Stream", { + RetentionPeriodHours: 48 + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts new file mode 100644 index 000000000..94bfe492d --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kinesis-streams-helper.test.ts @@ -0,0 +1,66 @@ +/** + * 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, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test minimal deployment with no properties +// -------------------------------------------------------------- +test('Test minimal deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildKinesisStream(stack); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike('AWS::Kinesis::Stream', { + Type: "AWS::Kinesis::Stream", + Properties: { + StreamEncryption: { + EncryptionType: "KMS" + } + } + }, ResourcePart.CompleteDefinition); +}); + +// -------------------------------------------------------------- +// Test deployment w/ custom properties +// -------------------------------------------------------------- +test('Test deployment w/ custom properties', () => { + // Stack + const stack = new Stack(); + // Helper setup + const encKey = defaults.buildEncryptionKey(stack); + // Helper declaration + defaults.buildKinesisStream(stack, { + encryptionKey: encKey, + kinesisStreamProps: { + streamName: 'myCustomKinesisStream' + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource('AWS::Kinesis::Stream', { + Name: 'myCustomKinesisStream' + }); + // Assertion 3 + expect(stack).toHaveResource('AWS::KMS::Key', { + EnableKeyRotation: true + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts new file mode 100644 index 000000000..c9e6c0d9e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/kms-helper.test.ts @@ -0,0 +1,60 @@ +/** + * 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, ResourcePart } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Test minimal deployment with no properties +// -------------------------------------------------------------- +test('Test minimal deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildEncryptionKey(stack); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + Type: "AWS::KMS::Key", + Properties: { + EnableKeyRotation: true + } + }, ResourcePart.CompleteDefinition); +}); + +// -------------------------------------------------------------- +// Test deployment w/ custom properties +// -------------------------------------------------------------- +test('Test minimal deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildEncryptionKey(stack, { + encryptionKeyProps: { + enableKeyRotation: false + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + Type: "AWS::KMS::Key", + Properties: { + EnableKeyRotation: false + } + }, ResourcePart.CompleteDefinition); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts new file mode 100644 index 000000000..18efbdacf --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/lambda-event-source.test.ts @@ -0,0 +1,39 @@ +/** + * 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 defaults from '../index'; +import { DynamoEventSourceProps } from '@aws-cdk/aws-lambda-event-sources'; +import * as lambda from '@aws-cdk/aws-lambda'; +import '@aws-cdk/assert/jest'; + +test('test DynamoEventSourceProps', () => { + const props = defaults.DynamoEventSourceProps(); + + expect(props).toEqual({ + startingPosition: "TRIM_HORIZON" + }); +}); + +test('test DynamoEventSourceProps override', () => { + const myProps: DynamoEventSourceProps = { + startingPosition: lambda.StartingPosition.LATEST, + batchSize: 1 + }; + + const props = defaults.DynamoEventSourceProps(myProps); + + expect(props).toEqual({ + batchSize: 1, + startingPosition: "LATEST" + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts new file mode 100644 index 000000000..9736045d7 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/lambda-func.test.ts @@ -0,0 +1,232 @@ +/** + * 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, ResourcePart } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/core'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as defaults from '../index'; +import '@aws-cdk/assert/jest'; +import { Duration } from '@aws-cdk/core'; + +test('snapshot test LambdaFunction default params', () => { + const stack = new Stack(); + + const lambdaFunctionProps: lambda.FunctionProps = { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + }; + + defaults.deployLambdaFunction(stack, lambdaFunctionProps); + + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test FunctionProps override code and runtime', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler' + }; + + defaults.deployLambdaFunction(stack, inProps); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "python3.6" + }); +}); + +test('test FunctionProps override timeout', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + timeout: Duration.seconds(5), + }; + + defaults.deployLambdaFunction(stack, inProps); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs12.x", + Timeout: 5 + }); +}); + +test('test FunctionProps for envrionment variable when runtime = NODEJS', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda`), + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler' + }; + + defaults.deployLambdaFunction(stack, inProps); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs10.x", + Environment: { + Variables: { + AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1' + } + } + }); + +}); + +test('test FunctionProps for no envrionment variable when runtime = PYTHON', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler' + }; + + defaults.deployLambdaFunction(stack, inProps); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Type: "AWS::Lambda::Function", + Properties: { + Code: { + S3Bucket: { + Ref: "AssetParametersb472c1cea6f4795d84eb1b97e37bfa1f79f1c744caebeb372f30dbf716299895S3Bucket0A3514D6" + }, + S3Key: { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + Ref: "AssetParametersb472c1cea6f4795d84eb1b97e37bfa1f79f1c744caebeb372f30dbf716299895S3VersionKey0DB6BEDE" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + Ref: "AssetParametersb472c1cea6f4795d84eb1b97e37bfa1f79f1c744caebeb372f30dbf716299895S3VersionKey0DB6BEDE" + } + ] + } + ] + } + ] + ] + } + }, + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "python3.6" + }, + DependsOn: [ + "LambdaFunctionServiceRole0C4CDE0B" + ] + }, ResourcePart.CompleteDefinition); + +}); + +test('test buildLambdaFunction with deploy = true', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }; + + defaults.buildLambdaFunction(stack, { + deployLambda: true, + lambdaFunctionProps: inProps + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "nodejs12.x" + }); +}); + +test('test buildLambdaFunction with deploy = false', () => { + const stack = new Stack(); + + const inProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda-test`), + runtime: lambda.Runtime.PYTHON_3_6, + handler: 'index.handler' + }; + + const fn = defaults.deployLambdaFunction(stack, inProps); + + defaults.buildLambdaFunction(stack, { + deployLambda: false, + existingLambdaObj: fn + }); + + expect(stack).toHaveResource('AWS::Lambda::Function', { + Handler: "index.handler", + Role: { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + Runtime: "python3.6" + }); +}); diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js b/source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js new file mode 100644 index 000000000..b12fff204 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/lambda-test/index.js @@ -0,0 +1,7 @@ +exports.handler = async function(event) { + return { + statusCode: 200, + headers: { 'Content-Type': 'text/plain' }, + body: `Hello, CDK! You've hit ${event.path}\n` + }; + }; \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/lambda/index.js b/source/patterns/@aws-solutions-konstruk/core/test/lambda/index.js new file mode 100644 index 000000000..4b3640c1e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/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/s3-bucket-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket-helper.test.ts new file mode 100644 index 000000000..dab7a659a --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket-helper.test.ts @@ -0,0 +1,59 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as defaults from '../index'; + +test('s3 bucket with default params', () => { + const stack = new Stack(); + defaults.buildS3Bucket(stack, { + deployBucket: true + }); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('s3 bucket with default params and bucket names', () => { + const stack = new Stack(); + const s3BucketProps: s3.BucketProps = { + bucketName: 'my-bucket' + }; + defaults.buildS3Bucket(stack, { + deployBucket: true, + bucketProps: s3BucketProps + }); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('s3 bucket with existingBucketObj', () => { + const stack = new Stack(); + + defaults.buildS3Bucket(stack, { + deployBucket: false, + existingBucketObj: new s3.Bucket(stack, 'my-bucket', {}) + }); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check exception for Missing existingBucketObj from props for deploy = false', () => { + const stack = new Stack(); + + try { + defaults.buildS3Bucket(stack, { + deployBucket: false + }); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts new file mode 100644 index 000000000..0fec10b05 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/s3-bucket.test.ts @@ -0,0 +1,184 @@ +/** + * 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 { Stack } from '@aws-cdk/core'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as defaults from '../index'; +import { overrideProps } from '../lib/utils'; +import '@aws-cdk/assert/jest'; + +test('s3 bucket with default params', () => { + const stack = new Stack(); + new s3.Bucket(stack, 'test-s3-defaults', defaults.DefaultS3Props()); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('test s3Bucket override versioningConfiguration', () => { + const stack = new Stack(); + const defaultProps: s3.CfnBucketProps = defaults.DefaultS3Props(); + + const inProps: s3.CfnBucketProps = { + versioningConfiguration: { + status: 'Disabled' + }, + }; + + const outProps = overrideProps(defaultProps, inProps); + new s3.CfnBucket(stack, 'test-s3-verioning', outProps); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + VersioningConfiguration: { + Status: 'Disabled' + } + }); +}); + +test('test s3Bucket override bucketEncryption', () => { + const stack = new Stack(); + const defaultProps: s3.CfnBucketProps = defaults.DefaultS3Props(); + + const inProps: s3.CfnBucketProps = { + bucketEncryption: { + serverSideEncryptionConfiguration : [{ + serverSideEncryptionByDefault: { + kmsMasterKeyId: 'mykeyid', + sseAlgorithm: 'aws:kms' + } + }] + }, + }; + + const outProps = overrideProps(defaultProps, inProps); + new s3.CfnBucket(stack, 'test-s3-encryption', outProps); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + BucketEncryption: { + ServerSideEncryptionConfiguration : [{ + ServerSideEncryptionByDefault: { + KMSMasterKeyID: 'mykeyid', + SSEAlgorithm: 'aws:kms' + } + }] + }, + }); +}); + +test('test s3Bucket override publicAccessBlockConfiguration', () => { + const stack = new Stack(); + const defaultProps: s3.CfnBucketProps = defaults.DefaultS3Props(); + + const inProps: s3.CfnBucketProps = { + publicAccessBlockConfiguration: { + blockPublicAcls: false, + blockPublicPolicy: true, + ignorePublicAcls: false, + restrictPublicBuckets: true + }, + }; + + const outProps = overrideProps(defaultProps, inProps); + new s3.CfnBucket(stack, 'test-s3-publicAccessBlock', outProps); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: false, + BlockPublicPolicy: true, + IgnorePublicAcls: false, + RestrictPublicBuckets: true + }, + }); +}); + +test('test s3Bucket add lifecycleConfiguration', () => { + const stack = new Stack(); + const defaultProps: s3.CfnBucketProps = defaults.DefaultS3Props(); + + const inProps: s3.CfnBucketProps = { + lifecycleConfiguration: { + rules: [ + { + status: 'Enabled', + expirationInDays: 365, + } + ] + } + }; + + const outProps = overrideProps(defaultProps, inProps); + new s3.CfnBucket(stack, 'test-s3-lifecycle', outProps); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + LifecycleConfiguration: { + Rules: [ + { + Status: 'Enabled', + ExpirationInDays: 365, + } + ] + } + }); +}); + +test('test s3Bucket add objectLock', () => { + const stack = new Stack(); + const defaultProps: s3.CfnBucketProps = defaults.DefaultS3Props(); + + const inProps: s3.CfnBucketProps = { + objectLockConfiguration: { + objectLockEnabled: 'Enabled', + rule: { + defaultRetention: { + days: 365 + } + } + }, + objectLockEnabled: true, + }; + + const outProps = overrideProps(defaultProps, inProps); + new s3.CfnBucket(stack, 'test-s3-objlock', outProps); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + ObjectLockConfiguration: { + ObjectLockEnabled: 'Enabled', + Rule: { + DefaultRetention: { + Days: 365 + } + } + }, + ObjectLockEnabled: true + }); +}); + +test('test s3Bucket override serverAccessLogsBucket', () => { + const stack = new Stack(); + + const myLoggingBucket: s3.Bucket = new s3.Bucket(stack, 'MyS3LoggingBucket', defaults.DefaultS3Props()); + + const myS3Props: s3.BucketProps = defaults.DefaultS3Props(myLoggingBucket); + + defaults.buildS3Bucket(stack, { + deployBucket: true, + bucketProps: myS3Props + }); + + expect(stack).toHaveResource("AWS::S3::Bucket", { + LoggingConfiguration: { + DestinationBucketName: { + Ref: "MyS3LoggingBucket119BE896" + } + } + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts new file mode 100644 index 000000000..32236f5e6 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/sns-helper.test.ts @@ -0,0 +1,77 @@ +/** + * 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'; + +// -------------------------------------------------------------- +// Test deployment with no properties +// -------------------------------------------------------------- +test('Test deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildTopic(stack); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ custom properties +// -------------------------------------------------------------- +test('Test deployment w/ custom properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildTopic(stack, { + topicProps: { + topicName: "custom-topic" + }, + enableEncryption: true + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SNS::Topic", { + TopicName: "custom-topic" + }); +}); + +// -------------------------------------------------------------- +// Test deployment w/ imported encryption key +// -------------------------------------------------------------- +test('Test deployment w/ imported encryption key', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildTopic(stack, { + topicProps: { + topicName: "custom-topic" + }, + enableEncryption: true, + encryptionKey: defaults.buildEncryptionKey(stack) + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); + // Assertion 2 + expect(stack).toHaveResource("AWS::SNS::Topic", { + TopicName: "custom-topic" + }); + // Assertion 3 + expect(stack).toHaveResource("AWS::KMS::Key", { + EnableKeyRotation: true + }); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts b/source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts new file mode 100644 index 000000000..eee43c39e --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/core/test/sqs-helper.test.ts @@ -0,0 +1,74 @@ +/** + * 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'; + +// -------------------------------------------------------------- +// Test minimal deployment with no properties +// -------------------------------------------------------------- +test('Test minimal deployment with no properties', () => { + // Stack + const stack = new Stack(); + // Helper declaration + defaults.buildQueue(stack, 'primary-queue'); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test deployment w/ custom properties +// -------------------------------------------------------------- +test('Test deployment w/ custom properties', () => { + // Stack + const stack = new Stack(); + // Helper setup + const encKey = defaults.buildEncryptionKey(stack); + // Helper declaration + defaults.buildQueue(stack, 'primary-queue', { + encryptionKey: encKey, + queueProps: { + description: "custom-queue-props" + } + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Test dead letter queue deployment/configuration +// -------------------------------------------------------------- +test('Test dead letter queue deployment/configuration', () => { + // Stack + const stack = new Stack(); + // Helper setup + const encKey = defaults.buildEncryptionKey(stack); + const dlq = defaults.buildQueue(stack, 'dead-letter-queue'); + const dlqi = defaults.buildDeadLetterQueue({ + deadLetterQueue: dlq, + maxReceiveCount: 3 + }); + // Helper declaration + defaults.buildQueue(stack, 'primary-queue', { + encryptionKey: encKey, + queueProps: { + description: "not-the-dead-letter-queue-props" + }, + deadLetterQueue: dlqi + }); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-konstruk/eslintrc.yml b/source/patterns/@aws-solutions-konstruk/eslintrc.yml new file mode 100644 index 000000000..6806f7be5 --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/eslintrc.yml @@ -0,0 +1,49 @@ +--- +env: + jest: true + node: true + +plugins: + - '@typescript-eslint' + - import + - license-header + +parser: '@typescript-eslint/parser' +parserOptions: + ecmaVersion: 2018 + sourceType: module + project: ./tsconfig.json + +extends: + - plugin:import/typescript + +settings: + import/parsers: + '@typescript-eslint/parser': ['.ts', '.tsx'] + import/resolver: + node: {} + typescript: + directory: ./tsconfig.json + +rules: + # Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` + '@typescript-eslint/no-require-imports': + - error + + # Require all imported dependencies are actually declared in package.json + 'import/no-extraneous-dependencies': + - error + - devDependencies: # Only allow importing devDependencies from: + - '**/test/**' # --> Unit tests + - '**/utils.ts' # --> uses deepmerge + optionalDependencies: false # Disallow importing optional dependencies (those shouldn't be in use in the project) + peerDependencies: false # Disallow importing peer dependencies (that aren't also direct dependencies) + + # Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) + 'import/no-unresolved': + - error + + #Check for license header + 'license-header/header': + - error + - ../license-header.js diff --git a/source/patterns/@aws-solutions-konstruk/license-header.js b/source/patterns/@aws-solutions-konstruk/license-header.js new file mode 100644 index 000000000..e3d94bb9f --- /dev/null +++ b/source/patterns/@aws-solutions-konstruk/license-header.js @@ -0,0 +1,12 @@ +/** + * 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. + */ \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/.eslintignore b/source/use_cases/aws-s3-static-website/.eslintignore new file mode 100644 index 000000000..8773c034a --- /dev/null +++ b/source/use_cases/aws-s3-static-website/.eslintignore @@ -0,0 +1,6 @@ +lib/*.js +test/*.js +bin/*.js +*.d.ts +coverage +test/lambda/index.js \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/.gitignore b/source/use_cases/aws-s3-static-website/.gitignore new file mode 100644 index 000000000..96e33d0f7 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/.gitignore @@ -0,0 +1,16 @@ +lib/*.js +test/*.js +bin/*.js +*.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/use_cases/aws-s3-static-website/.npmignore b/source/use_cases/aws-s3-static-website/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/.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/use_cases/aws-s3-static-website/README.md b/source/use_cases/aws-s3-static-website/README.md new file mode 100644 index 000000000..258a17b28 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/README.md @@ -0,0 +1,23 @@ +# AWS S3 Static Website Use Case + +This use case implements a static website that delivers HTML, JavaScript, images, video and other files to your website visitors and contain no application code. + +## Architecture +The application architecture uses an Amazon CloudFront distribution, Amazon S3 and AWS lambda based custom resource to copy the static website content for Wild Rydes demo website. +![Architecture Diagram](architecture.png) + +## Deployment steps +Below are the steps to deploy the use case: + +``` +npm run build + +cdk deploy + +``` + +## Deployment Verification +After the stack is deployed successfully, go to the Outputs tab in AWS Cloudformation console, it should show the 'websiteURL', click on the link and enjoy the Wile Rydes Unicorn website. + +*** +© Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/architecture.png b/source/use_cases/aws-s3-static-website/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..967bc554f08d66ed89aeb70cb9fa2faef6d5121e GIT binary patch literal 172868 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
    -1+S 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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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 literal 0 HcmV?d00001 diff --git a/source/use_cases/aws-s3-static-website/bin/s3-static-site-app.ts b/source/use_cases/aws-s3-static-website/bin/s3-static-site-app.ts new file mode 100644 index 000000000..4bb90921c --- /dev/null +++ b/source/use_cases/aws-s3-static-website/bin/s3-static-site-app.ts @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +/** + * 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 'source-map-support/register'; +import * as cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; + +const app = new cdk.App(); +const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); +stack.templateOptions.description = 'Creates a static website using S3 for the Wild Rydes serverless web application workshop'; \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/cdk.json b/source/use_cases/aws-s3-static-website/cdk.json new file mode 100644 index 000000000..ed6e4df88 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/s3-static-site-app.ts" +} \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/lib/lambda/copy_s3_objects.py b/source/use_cases/aws-s3-static-website/lib/lambda/copy_s3_objects.py new file mode 100644 index 000000000..cdfd4406c --- /dev/null +++ b/source/use_cases/aws-s3-static-website/lib/lambda/copy_s3_objects.py @@ -0,0 +1,56 @@ +import os +import json +import boto3 +from botocore.exceptions import ClientError +client = boto3.client('s3') + +import logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +def on_event(event, context): + logger.info("Received event: %s" % json.dumps(event)) + request_type = event['RequestType'] + if request_type == 'Create': return on_create(event) + if request_type == 'Update': return on_create(event) + if request_type == 'Delete': return on_delete(event) + raise Exception("Invalid request type: %s" % request_type) + +def on_create(event): + source_bucket = event['ResourceProperties']['SourceBucket'] + source_prefix = event['ResourceProperties'].get('SourcePrefix') or '' + bucket = event['ResourceProperties']['Bucket'] + prefix = event['ResourceProperties'].get('Prefix') or '' + try: + copy_objects(source_bucket, source_prefix, bucket, prefix) + except ClientError as e: + logger.error('Error: %s', e) + raise e + return + +def on_delete(event): + bucket = event['ResourceProperties']['Bucket'] + prefix = event['ResourceProperties'].get('Prefix') or '' + try: + delete_objects(bucket, prefix) + except ClientError as e: + logger.error('Error: %s', e) + raise e + return + +def copy_objects(source_bucket, source_prefix, bucket, prefix): + paginator = client.get_paginator('list_objects_v2') + page_iterator = paginator.paginate(Bucket=source_bucket, Prefix=source_prefix) + for key in {x['Key'] for page in page_iterator for x in page['Contents']}: + dest_key = os.path.join(prefix, os.path.relpath(key, source_prefix)) + if not key.endswith('/'): + logger.info("copy %s to %s".format(key, dest_key)) + client.copy_object(CopySource={'Bucket': source_bucket, 'Key': key}, Bucket=bucket, Key = dest_key) + return + +def delete_objects(bucket, prefix): + paginator = client.get_paginator('list_objects_v2') + page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix) + objects = [{'Key': x['Key']} for page in page_iterator for x in page['Contents']] + client.delete_objects(Bucket=bucket, Delete={'Objects': objects}) + return \ No newline at end of file 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 new file mode 100644 index 000000000..a3df7e9fc --- /dev/null +++ b/source/use_cases/aws-s3-static-website/lib/s3-static-site-stack.ts @@ -0,0 +1,77 @@ +/** + * 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 { Construct, Stack, StackProps, Duration, CfnOutput } from '@aws-cdk/core'; +import { CloudFrontToS3 } from '@aws-solutions-konstruk/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'; +import { PolicyStatement } from '@aws-cdk/aws-iam'; + +export class S3StaticWebsiteStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceBucket: string = 'wildrydes-us-east-1'; + const sourcePrefix: string = 'WebApplication/1_StaticWebHosting/website/'; + + const konstruk = new CloudFrontToS3(this, 'CloudFrontToS3', { + deployBucket: true + }); + const targetBucket: string = konstruk.bucket().bucketName; + + const lambdaFunc = new lambda.Function(this, 'copyObjHandler', { + runtime: lambda.Runtime.PYTHON_3_8, + handler: 'copy_s3_objects.on_event', + code: lambda.Code.fromAsset(`${__dirname}/lambda`), + timeout: Duration.minutes(5), + initialPolicy: [ + new PolicyStatement({ + actions: ["s3:GetObject", + "s3:ListBucket"], + resources: [`arn:aws:s3:::${sourceBucket}`, + `arn:aws:s3:::${sourceBucket}/${sourcePrefix}*`] + }), + new PolicyStatement({ + actions: ["s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject"], + resources: [`arn:aws:s3:::${targetBucket}`, + `arn:aws:s3:::${targetBucket}/*`] + }), + ] + }); + + const customResourceProvider = new Provider(this, 'CustomResourceProvider', { + onEventHandler: lambdaFunc + }); + + new CustomResource(this, 'CustomResource', { + provider: customResourceProvider, + properties: { + SourceBucket: sourceBucket, + SourcePrefix: sourcePrefix, + Bucket: targetBucket + } + }); + + new CfnOutput(this, 'websiteURL', { + value: 'https://' + konstruk.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 new file mode 100644 index 000000000..be6e3dd86 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/package.json @@ -0,0 +1,52 @@ +{ + "name": "@aws-solutions-konstruk/aws-s3-static-website", + "version": "0.8.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", + "directory": "source/use_cases/aws-s3-static-website" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc", + "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", + "build+lint+test": "npm run build && npm run lint && npm test && npm run integ-assert" + }, + "dependencies": { + "@aws-solutions-konstruk/aws-cloudfront-s3": "~0.8.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/custom-resources": "~1.25.0", + "@aws-cdk/aws-cloudformation": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "source-map-support": "^0.5.16" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + } +} diff --git a/source/use_cases/aws-s3-static-website/test/__snapshots__/s3-static-site-stack.test.js.snap b/source/use_cases/aws-s3-static-website/test/__snapshots__/s3-static-site-stack.test.js.snap new file mode 100644 index 000000000..2ab898e31 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/test/__snapshots__/s3-static-site-stack.test.js.snap @@ -0,0 +1,626 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`default stack 1`] = ` +Object { + "Outputs": Object { + "websiteURL": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E", + "DomainName", + ], + }, + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debArtifactHashA2BE913F": Object { + "Description": "Artifact hash for asset \\"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\\"", + "Type": "String", + }, + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3BucketC3836BD9": Object { + "Description": "S3 bucket for asset \\"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\\"", + "Type": "String", + }, + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9": Object { + "Description": "S3 key for asset version \\"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": Object { + "Description": "Artifact hash for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": Object { + "Description": "S3 bucket for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": Object { + "Description": "S3 key for asset version \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + }, + "Resources": Object { + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "DomainName": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "RegionalDomainName", + ], + }, + "Id": "origin1", + "S3OriginConfig": Object { + "OriginAccessIdentity": Object { + "Fn::Join": Array [ + "", + Array [ + "origin-access-identity/cloudfront/", + Object { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + }, + ], + ], + }, + }, + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91": Object { + "Properties": Object { + "CloudFrontOriginAccessIdentityConfig": Object { + "Comment": "Access S3 bucket content only through CloudFront", + }, + }, + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + }, + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "CloudFrontToS3S3Bucket9CE6AB04": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "CloudFrontToS3S3BucketPolicy2495300D": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "F16", + "reason": "Public website bucket policy requires a wildcard principal", + }, + ], + }, + }, + "Properties": Object { + "Bucket": Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + Object { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + }, + ], + ], + }, + }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Principal": Object { + "CanonicalUser": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + "S3CanonicalUserId", + ], + }, + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "CloudFrontToS3S3LoggingBucketEF5CD8B2": 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", + }, + "CustomResource": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "Bucket": Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn", + ], + }, + "SourceBucket": "wildrydes-us-east-1", + "SourcePrefix": "WebApplication/1_StaticWebHosting/website/", + }, + "Type": "AWS::CloudFormation::CustomResource", + "UpdateReplacePolicy": "Delete", + }, + "CustomResourceProviderframeworkonEvent0AA4376C": Object { + "DependsOn": Array [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "USER_ON_EVENT_FUNCTION_ARN": Object { + "Fn::GetAtt": Array [ + "copyObjHandlerDA1C4669", + "Arn", + ], + }, + }, + }, + "Handler": "framework.onEvent", + "Role": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "copyObjHandlerDA1C4669", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": Array [ + Object { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "copyObjHandlerDA1C4669": Object { + "DependsOn": Array [ + "copyObjHandlerServiceRoleDefaultPolicyFCA51C18", + "copyObjHandlerServiceRoleA0ECE649", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3BucketC3836BD9", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "copy_s3_objects.on_event", + "Role": Object { + "Fn::GetAtt": Array [ + "copyObjHandlerServiceRoleA0ECE649", + "Arn", + ], + }, + "Runtime": "python3.8", + "Timeout": 300, + }, + "Type": "AWS::Lambda::Function", + }, + "copyObjHandlerServiceRoleA0ECE649": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "copyObjHandlerServiceRoleDefaultPolicyFCA51C18": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject", + "s3:ListBucket", + ], + "Effect": "Allow", + "Resource": Array [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*", + ], + }, + Object { + "Action": Array [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:s3:::", + Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:s3:::", + Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "copyObjHandlerServiceRoleDefaultPolicyFCA51C18", + "Roles": Array [ + Object { + "Ref": "copyObjHandlerServiceRoleA0ECE649", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.expected.json b/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.expected.json new file mode 100644 index 000000000..e8e701931 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.expected.json @@ -0,0 +1,622 @@ +{ + "Resources": { + "CloudFrontToS3S3LoggingBucketEF5CD8B2": { + "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" + } + ] + } + } + }, + "CloudFrontToS3S3Bucket9CE6AB04": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2" + } + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CloudFrontToS3S3BucketPolicy2495300D": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91" + } + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Principal": { + "CanonicalUser": { + "Fn::GetAtt": [ + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + "S3CanonicalUserId" + ] + } + }, + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "F16", + "reason": "Public website bucket policy requires a wildcard principal" + } + ] + } + } + }, + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B": { + "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 CloudFront Distribution" + }, + { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91": { + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + "Properties": { + "CloudFrontOriginAccessIdentityConfig": { + "Comment": "Access S3 bucket content only through CloudFront" + } + } + }, + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B", + "RegionalDomainName" + ] + }, + "IncludeCookies": false + }, + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "S3OriginConfig": { + "OriginAccessIdentity": { + "Fn::Join": [ + "", + [ + "origin-access-identity/cloudfront/", + { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91" + } + ] + ] + } + } + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "copyObjHandlerServiceRoleA0ECE649": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "copyObjHandlerServiceRoleDefaultPolicyFCA51C18": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*" + ] + }, + { + "Action": [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "copyObjHandlerServiceRoleDefaultPolicyFCA51C18", + "Roles": [ + { + "Ref": "copyObjHandlerServiceRoleA0ECE649" + } + ] + } + }, + "copyObjHandlerDA1C4669": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3BucketC3836BD9" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "copy_s3_objects.on_event", + "Role": { + "Fn::GetAtt": [ + "copyObjHandlerServiceRoleA0ECE649", + "Arn" + ] + }, + "Runtime": "python3.8", + "Timeout": 300 + }, + "DependsOn": [ + "copyObjHandlerServiceRoleDefaultPolicyFCA51C18", + "copyObjHandlerServiceRoleA0ECE649" + ] + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "copyObjHandlerDA1C4669", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": [ + { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + } + ] + } + }, + "CustomResourceProviderframeworkonEvent0AA4376C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "copyObjHandlerDA1C4669", + "Arn" + ] + } + } + }, + "Timeout": 900 + }, + "DependsOn": [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + ] + }, + "CustomResource": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn" + ] + }, + "SourceBucket": "wildrydes-us-east-1", + "SourcePrefix": "WebApplication/1_StaticWebHosting/website/", + "Bucket": { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3BucketC3836BD9": { + "Type": "String", + "Description": "S3 bucket for asset \"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\"" + }, + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debS3VersionKeyEDA18BF9": { + "Type": "String", + "Description": "S3 key for asset version \"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\"" + }, + "AssetParameters2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503debArtifactHashA2BE913F": { + "Type": "String", + "Description": "Artifact hash for asset \"2a96a41ef8e6db639865e8dc7826848e60ba75ebdb553c6c9bf2f961ef503deb\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": { + "Type": "String", + "Description": "S3 bucket for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": { + "Type": "String", + "Description": "S3 key for asset version \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": { + "Type": "String", + "Description": "Artifact hash for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + } + }, + "Outputs": { + "websiteURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Fn::GetAtt": [ + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E", + "DomainName" + ] + } + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.ts b/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.ts new file mode 100644 index 000000000..6e8a10593 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/test/integ.basic-deployment.ts @@ -0,0 +1,18 @@ +/** + * 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 cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; + +const app = new cdk.App(); +new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/test/s3-static-site-stack.test.ts b/source/use_cases/aws-s3-static-website/test/s3-static-site-stack.test.ts new file mode 100644 index 000000000..e42365947 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/test/s3-static-site-stack.test.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. + */ + +import * as cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +test('default stack', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check s3 bucket encryption setting', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::S3::Bucket", { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: "AES256" + } + } + ] + } + }); +}); + +test('check s3 bucket public access setting', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true + } + }); +}); + +test('check CR lambda function permissions', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "s3:GetObject", + "s3:ListBucket" + ], + Effect: "Allow", + Resource: [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*" + ] + }, + { + Action: [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject" + ], + Effect: "Allow", + Resource: [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "CloudFrontToS3S3Bucket9CE6AB04" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "CloudFrontToS3S3Bucket9CE6AB04" + }, + "/*" + ] + ] + } + ] + } + ], + Version: "2012-10-17" + }, + PolicyName: "copyObjHandlerServiceRoleDefaultPolicyFCA51C18", + Roles: [ + { + Ref: "copyObjHandlerServiceRoleA0ECE649" + } + ] + }); +}); \ No newline at end of file diff --git a/source/use_cases/aws-s3-static-website/tsconfig.json b/source/use_cases/aws-s3-static-website/tsconfig.json new file mode 100644 index 000000000..27d10a655 --- /dev/null +++ b/source/use_cases/aws-s3-static-website/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ "es2018" ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/source/use_cases/aws-serverless-image-handler/.eslintignore b/source/use_cases/aws-serverless-image-handler/.eslintignore new file mode 100644 index 000000000..f829c06de --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/.eslintignore @@ -0,0 +1,7 @@ +lib/*.js +test/*.js +*.d.ts +coverage +test/lambda/index.js +lib/lambda/image-handler/**/* +cdk.out \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/.gitignore b/source/use_cases/aws-serverless-image-handler/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.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/use_cases/aws-serverless-image-handler/.npmignore b/source/use_cases/aws-serverless-image-handler/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/.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/use_cases/aws-serverless-image-handler/README.md b/source/use_cases/aws-serverless-image-handler/README.md new file mode 100644 index 000000000..b350f4013 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/README.md @@ -0,0 +1,36 @@ +# AWS Serverless Image Handler + +This use case construct 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. + +Here is a minimal deployable pattern definition: + +``` +const { ServerlessImageHandler } = require('@aws-konstruk/aws-serverless-image-handler'); + +new ServerlessImageHandler(stack, 'ServerlessImageHandlerPattern', { + deployLambda: true, + lambdaFunctionProps: { + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + code: lambda.Code.asset(`${__dirname}/lambda`) + } +}); + +``` + +## Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +|deployLambda|`boolean`|Whether to create a new Lambda function or use an existing Lambda function. If set to false, you must provide an existing function for the `existingLambdaObj` property.| +|existingLambdaObj?|`lambda.Function`|An optional, existing Lambda function. This property is required if `deployLambda` is set to false.| +|lambdaFunctionProps?|`lambda.FunctionProps`|Optional user-provided props to override the default props for the Lambda function. This property is only required if `deployLambda` is set to true.| +|apiGatewayProps?|`api.LambdaRestApiProps`|Optional user-provided props to override the default props for the API.| + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/architecture.png b/source/use_cases/aws-serverless-image-handler/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..4e617ab6e85f83487287e37733f9ce483e5d6723 GIT binary patch literal 98366 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�_LfTmp zb3a{m|4qzT(J96`?EJdc)W`Aef17nHIronkLpRl4(+zt69A=zhA?!#8c9H6N04`60 zcJ(iIKQtNS*NFc3QB>Xe;Js=v`Vq9PYX|-}p&@HaWnZ)Z3*FBTE&`3nIsvziQH74n zOL_ixJ30YhbOh|;u^dUD7Pm3ZBm9`%=le3%c)!e0aAh6=iFa~z6TbFXQ58CNf z+K)}s{5v<;ifREd?D5iJBzssP&344qUCg;QVC5QnTr)Hl!o(=}BjNY32gtl=FEY8exxzOpc)mlfD&3*c0hACOOM%w)lpBRY`Piy@_90HaSu;n zJ<@NW163bqyBJQ@SQGMuu({#=%Lh{`IZ=^0R=c~$O0Xo&a8o?xo|t@Qs*|Rh$2)Fu zJdK4)@!fSJ_3j3|*OzFH%R31hH&b%EYBC#krc0!?=(uo@cw#$KFXlZkWib$m3EMw) z+BV+=a{MvtfrCrToS762d&mlTY}2sP%jxs*7R>V=0Ns)@yyRfI%kMv!C!GC(XR50N9wa1fqPCzG`A4qH+Xa0K58@O{nVk zuH&VPp#!t8IP@{__6*~Z9ilp&=RL$#ZdwV_cZnr7{&3~EgI)pAmUe5Vt zdRY2suJIO-Z3FP{=RGvotfFF8<&@sh#2Q@sjQ)}tCyKp6QGs7sLOFK1&kc*YH62XY z^xHJeE7xXN>J^uFi{zOM>e#{kwrp5wUyLz{lt%~yPv zeyyexVdCK-qCXWr4o9Q$9Q{9brXbe9j+a}Bp!Bj2} znXB$TPaqJ>@^2X&t3?T3!Ohfd9Nr2ZwM`fOEfnT+V<6|dwOUI5Iuxd}>m$ZcSg_|& z-{|bLWcn{uc|TmbQ4Lf<3ytiJjoiO7!dtWn_wI60a@YH@0~(H+&L<`63xpg2>r=8@4p z$H!Fef}gs3o7h@hra?=-DYQ8Ctk{JoLANyc&i1ziBY}Ui)WkYEgQ6lcbYx4Cey|kn zo8o>ZSxvJF+`G=zc1jqQEqBn;9Tg*%09N#OlD8S#&u=E^w(pmHINsQK7^G^A+n=k2 z-83VL^o2~e(YND)F^A6U`JiiiO4`lOwoJs(N@K4q8Ppe44Bd&nGBtcz5~@4Ll{;Hq3Bbt?M%ida7i4e3$pV58hg6#)_>H|MJy zOUV;+(o+SzVT^#q`Jt1hK%2ddZfyAVJK5HZNu9ryJan5MbLVPJHZ@xRf|Aq(5kctR zz?aYJ8fK6rQ(V z$=_&H^|G>ohkX9z01cRwR_)rN0$Z)%<&qhVDB5|NZ}n1?URjQ}zAxZulQYoJVKqFKtb| KzsfXh-~2C~^N*$g literal 0 HcmV?d00001 diff --git a/source/use_cases/aws-serverless-image-handler/lib/index.ts b/source/use_cases/aws-serverless-image-handler/lib/index.ts new file mode 100644 index 000000000..9047fd8c5 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/index.ts @@ -0,0 +1,243 @@ +/** + * 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 * as defaults from '@aws-solutions-konstruk/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'; + +/** + * The properties for the ServerlessImageHandler class. + */ +export interface ServerlessImageHandlerProps { + /** + * Whether or not to emable Cross-Origin Resource Sharing (CORS) for the image handler API. + * + * @default - false. + */ + readonly corsEnabled?: boolean + /** + * The CORS origin to use for the image handler API. This property is only required if `corsEnabled` is set to true. + * + * @default - none. + */ + readonly corsOrigin?: string + /** + * One or more buckets within the deployment account to be used for storing/sourcing original image files. + * + * @default - required. + */ + readonly sourceBuckets: string + /** + * The amount of time for CloudWatch log entries from this solution to be retained. + * + * @default - 1 day. + */ + readonly logRetentionPeriod?: number, + /** + * Whether or not to accept/enable automatic WebP based on Accept- headers. + * + * @default - false. + */ + readonly autoWebP?: boolean, + /** + * Optional user provided props to override the default props for each resource. + * + * @default - undefined/optional. + */ + readonly customProps?: ServerlessImageHandlerCustomProps +} + +/** + * Custom properties for the ServerlessImageHandler class. + */ +export interface ServerlessImageHandlerCustomProps { + /** + * Optional user provided props to override the default props for the CloudFront distribution. + * + * @default - false. + */ + readonly cloudFrontDistributionProps?: cloudFront.CloudFrontWebDistributionProps | any, + /** + * Optional user provided props to override the default props for the API Gateway REST API. + * + * @default - none. + */ + readonly apiGatewayProps?: apiGateway.RestApiProps | any, + /** + * Optional user provided props to override the default props for the Lambda function. + * + * @default - none. + */ + readonly lambdaFunctionProps?: lambda.FunctionProps | any, + /** + * Optional user provided props to override the default props for the S3 bucket. + * + * @default - none. + */ + readonly bucketProps?: s3.BucketProps | any, + /** + * Optional user provided props to override the default permissions for the S3 bucket. + * + * @default - none. + */ + readonly bucketPermissions?: string[] +} + +/** + * @summary The ServerlessImageHandler class. + */ +export class ServerlessImageHandler extends Construct { + // Private variables + private cloudFrontApiGatewayLambda: CloudFrontToApiGatewayToLambda; + private lambdaS3: LambdaToS3; + private customProps: any; + + /** + * @summary Constructs a new instance of the ServerlessImageHandler class. + * @param {cdk.App} scope - represents the scope for all the resources. + * @param {string} id - this is a a scope-unique id. + * @param {ServerlessImageHandlerProps} props - user provided props for the construct + * @since 0.8.0 + * @access public + */ + constructor(scope: Construct, id: string, props: ServerlessImageHandlerProps) { + super(scope, id); + + // If customProps is undefined, define it + this.customProps = (props.customProps === undefined) ? {} : props.customProps; + + // Use case specific properties for the Lambda function + const useCaseFunctionProps: lambda.FunctionProps = { + code: lambda.Code.asset(`${__dirname}/lambda/image-handler`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler', + environment: { + AUTO_WEBP: (props.autoWebP) ? 'Yes' : 'No', + CORS_ENABLED: (props.corsEnabled) ? 'Yes' : 'No', + CORS_ORIGIN: (props.corsOrigin) ? props.corsOrigin : '' + } + }; + const functionProps = (this.customProps.lambdaFunctionProps) ? + defaults.overrideProps(useCaseFunctionProps, this.customProps.lambdaFunctionProps) : useCaseFunctionProps; + + // Use case specific properties for the API Gateway + const useCaseApiProps: apiGateway.RestApiProps = { + binaryMediaTypes: [ "*/*" ] + }; + const apiProps = (this.customProps.apiGatewayProps) ? + defaults.overrideProps(useCaseApiProps, this.customProps.apiGatewayProps) : useCaseApiProps; + + // Build the CloudFrontToApiGatewayToLambda pattern + this.cloudFrontApiGatewayLambda = new CloudFrontToApiGatewayToLambda(this, 'CloudFrontApiGatewayLambda', { + cloudFrontDistributionProps: (this.customProps.cloudFrontDistributionProps) ? this.customProps.cloudFrontDistributionProps : undefined, + apiGatewayProps: apiProps, + deployLambda: true, + lambdaFunctionProps: functionProps + }); + const existingLambdaFn = this.cloudFrontApiGatewayLambda.lambdaFunction(); + + // Build the LambdaToS3 pattern + this.lambdaS3 = new LambdaToS3(this, 'ExistingLambdaS3', { + deployLambda: false, + existingLambdaObj: existingLambdaFn, + bucketProps: this.customProps.bucketProps, + bucketPermissions: (this.customProps.bucketPermissions) ? this.customProps.bucketPermissions : undefined + }); + + // Add additional permissions for Lambda to source original images from any bucket in the account + const lambdaSourcingPolicyStmt = new iam.PolicyStatement({ + effect: iam.Effect.ALLOW + }); + lambdaSourcingPolicyStmt.addResources('arn:aws:s3:::*'); + lambdaSourcingPolicyStmt.addActions('s3:GetObject*', 's3:GetBucket*', 's3:List*'); + + // Add additional permissions for Lambda to access Rekognition services + const lambdaRekognitionPolicyStmt = new iam.PolicyStatement({ + effect: iam.Effect.ALLOW + }); + lambdaRekognitionPolicyStmt.addResources('*'); + lambdaRekognitionPolicyStmt.addActions('rekognition:DetectFaces'); + + // Append the additional permissions to an inline policy + const inlinePolicy = new iam.Policy(this, 'LambdaS3AccessPolicy', { + statements: [ lambdaSourcingPolicyStmt, lambdaRekognitionPolicyStmt ] + }); + + // Add cfn_nag suppression for Rekognition wildcard resource + const rawInlinePolicy: iam.CfnPolicy = inlinePolicy.node.findChild('Resource') as iam.CfnPolicy; + rawInlinePolicy.cfnOptions.metadata = { + cfn_nag: { + rules_to_suppress: [{ + id: 'W12', + reason: `Specified Rekognition action needs wildcard resource.` + }] + } + }; + + // Attach the inline policy to the Lambda function role + existingLambdaFn.role?.attachInlinePolicy(inlinePolicy); + + // Add the SOURCE_BUCKETS environment variable to the Lambda function + const bucketsArr = (props.sourceBuckets !== "") ? props.sourceBuckets.split(',') : []; + bucketsArr.push(this.lambdaS3.s3Bucket().bucketName); + const bucketsStr = bucketsArr.toString().replace(/\s+/g, ''); + this.cloudFrontApiGatewayLambda.lambdaFunction().addEnvironment("SOURCE_BUCKETS", bucketsStr); + } + + /** + * @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 cloudFrontDistribution(): cloudFront.CloudFrontWebDistribution { + return this.cloudFrontApiGatewayLambda.cloudFrontWebDistribution(); + } + + /** + * @summary Returns an instance of apiGateway.RestApi created by the construct. + * @returns { apiGateway.RestApi } Instance of RestApi created by the construct. + * @since 0.8.0 + * @access public + */ + public apiGateway(): apiGateway.RestApi { + return this.cloudFrontApiGatewayLambda.restApi(); + } + + /** + * @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.cloudFrontApiGatewayLambda.lambdaFunction(); + } + + /** + * @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 s3Bucket(): s3.Bucket { + return this.lambdaS3.s3Bucket(); + } +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-handler.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-handler.js new file mode 100755 index 000000000..49bf4b615 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-handler.js @@ -0,0 +1,240 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const AWS = require('aws-sdk'); +const sharp = require('sharp'); + +class ImageHandler { + + /** + * Main method for processing image requests and outputting modified images. + * @param {ImageRequest} request - An ImageRequest object. + */ + async process(request) { + const originalImage = request.originalImage; + const edits = request.edits; + if (edits !== undefined) { + const modifiedImage = await this.applyEdits(originalImage, edits); + if (request.outputFormat !== undefined) { + modifiedImage.toFormat(request.outputFormat); + } + const bufferImage = await modifiedImage.toBuffer(); + return bufferImage.toString('base64'); + } else { + return originalImage.toString('base64'); + } + } + + /** + * Applies image modifications to the original image based on edits + * specified in the ImageRequest. + * @param {Buffer} originalImage - The original image. + * @param {Object} edits - The edits to be made to the original image. + */ + async applyEdits(originalImage, edits) { + if (edits.resize === undefined) { + edits.resize = {}; + edits.resize.fit = 'inside'; + } + + const image = sharp(originalImage, { failOnError: false }); + const metadata = await image.metadata(); + const keys = Object.keys(edits); + const values = Object.values(edits); + + // Apply the image edits + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = values[i]; + if (key === 'overlayWith') { + let imageMetadata = metadata; + if (edits.resize) { + let imageBuffer = await image.toBuffer(); + imageMetadata = await sharp(imageBuffer).resize({ edits: { resize: edits.resize }}).metadata(); + } + + const { bucket, key, wRatio, hRatio, alpha } = value; + const overlay = await this.getOverlayImage(bucket, key, wRatio, hRatio, alpha, imageMetadata); + const overlayMetadata = await sharp(overlay).metadata(); + + let { options } = value; + if (options) { + if (options.left) { + let left = options.left; + if (left.endsWith('p')) { + left = parseInt(left.replace('p', '')); + if (left < 0) { + left = imageMetadata.width + (imageMetadata.width * left / 100) - overlayMetadata.width; + } else { + left = imageMetadata.width * left / 100; + } + } else { + left = parseInt(left); + if (left < 0) { + left = imageMetadata.width + left - overlayMetadata.width; + } + } + options.left = parseInt(left); + } + if (options.top) { + let top = options.top; + if (top.endsWith('p')) { + top = parseInt(top.replace('p', '')); + if (top < 0) { + top = imageMetadata.height + (imageMetadata.height * top / 100) - overlayMetadata.height; + } else { + top = imageMetadata.height * top / 100; + } + } else { + top = parseInt(top); + if (top < 0) { + top = imageMetadata.height + top - overlayMetadata.height; + } + } + options.top = parseInt(top); + } + } + + const params = [{ ...options, input: overlay }]; + image.composite(params); + } else if (key === 'smartCrop') { + const options = value; + const imageBuffer = await image.toBuffer(); + const boundingBox = await this.getBoundingBox(imageBuffer, options.faceIndex); + const cropArea = this.getCropArea(boundingBox, options, metadata); + try { + image.extract(cropArea) + } catch (err) { + throw ({ + status: 400, + code: 'SmartCrop::PaddingOutOfBounds', + message: 'The padding value you provided exceeds the boundaries of the original image. Please try choosing a smaller value or applying padding via Sharp for greater specificity.' + }); + } + } else { + image[key](value); + } + } + // Return the modified image + return image; + } + + /** + * Gets an image to be used as an overlay to the primary image from an + * Amazon S3 bucket. + * @param {string} bucket - The name of the bucket containing the overlay. + * @param {string} key - The keyname corresponding to the overlay. + */ + async getOverlayImage(bucket, key, wRatio, hRatio, alpha, sourceImageMetadata) { + const s3 = new AWS.S3(); + const params = { Bucket: bucket, Key: key }; + try { + const { width, height } = sourceImageMetadata; + const overlayImage = await s3.getObject(params).promise(); + let resize = { + fit: 'inside' + } + + // Set width and height of the watermark image based on the ratio + const zeroToHundred = /^(100|[1-9]?[0-9])$/; + if (zeroToHundred.test(wRatio)) { + resize['width'] = parseInt(width * wRatio / 100); + } + if (zeroToHundred.test(hRatio)) { + resize['height'] = parseInt(height * hRatio / 100); + } + + // If alpha is not within 0-100, the default alpha is 0 (fully opaque). + if (zeroToHundred.test(alpha)) { + alpha = parseInt(alpha); + } else { + alpha = 0; + } + + const convertedImage = await sharp(overlayImage.Body) + .resize(resize) + .composite([{ + input: Buffer.from([255, 255, 255, 255 * (1 - alpha / 100)]), + raw: { + width: 1, + height: 1, + channels: 4 + }, + tile: true, + blend: 'dest-in' + }]).toBuffer(); + return Promise.resolve(convertedImage); + } catch (err) { + return Promise.reject({ + status: err.statusCode ? err.statusCode : 500, + code: err.code, + message: err.message + }) + } + } + + /** + * Calculates the crop area for a smart-cropped image based on the bounding + * box data returned by Amazon Rekognition, as well as padding options and + * the image metadata. + * @param {Object} boundingBox - The boudning box of the detected face. + * @param {Object} options - Set of options for smart cropping. + * @param {Object} metadata - Sharp image metadata. + */ + getCropArea(boundingBox, options, metadata) { + const padding = (options.padding !== undefined) ? parseFloat(options.padding) : 0; + // Calculate the smart crop area + const cropArea = { + left : parseInt((boundingBox.Left*metadata.width)-padding), + top : parseInt((boundingBox.Top*metadata.height)-padding), + width : parseInt((boundingBox.Width*metadata.width)+(padding*2)), + height : parseInt((boundingBox.Height*metadata.height)+(padding*2)), + } + // Return the crop area + return cropArea; + } + + /** + * Gets the bounding box of the specified face index within an image, if specified. + * @param {Sharp} imageBuffer - The original image. + * @param {Integer} faceIndex - The zero-based face index value, moving from 0 and up as + * confidence decreases for detected faces within the image. + */ + async getBoundingBox(imageBuffer, faceIndex) { + const rekognition = new AWS.Rekognition(); + const params = { Image: { Bytes: imageBuffer }}; + const faceIdx = (faceIndex !== undefined) ? faceIndex : 0; + try { + const response = await rekognition.detectFaces(params).promise(); + return Promise.resolve(response.FaceDetails[faceIdx].BoundingBox); + } catch (err) { + console.log(err); + if (err.message === "Cannot read property 'BoundingBox' of undefined") { + return Promise.reject({ + status: 400, + code: 'SmartCrop::FaceIndexOutOfRange', + message: 'You have provided a FaceIndex value that exceeds the length of the zero-based detectedFaces array. Please specify a value that is in-range.' + }) + } else { + return Promise.reject({ + status: 500, + code: err.code, + message: err.message + }) + } + } + } +} + +// Exports +module.exports = ImageHandler; diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-request.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-request.js new file mode 100755 index 000000000..f8224543a --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/image-request.js @@ -0,0 +1,305 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const ThumborMapping = require('./thumbor-mapping'); + +class ImageRequest { + + /** + * Initializer function for creating a new image request, used by the image + * handler to perform image modifications. + * @param {Object} event - Lambda request body. + */ + async setup(event) { + try { + this.requestType = this.parseRequestType(event); + this.bucket = this.parseImageBucket(event, this.requestType); + this.key = this.parseImageKey(event, this.requestType); + this.edits = this.parseImageEdits(event, this.requestType); + this.originalImage = await this.getOriginalImage(this.bucket, this.key); + + /* Decide the output format of the image. + * 1) If the format is provided, the output format is the provided format. + * 2) If headers contain "Accept: image/webp", the output format is webp. + * 3) Use the default image format for the rest of cases. + */ + let outputFormat = this.getOutputFormat(event); + if (this.edits && this.edits.toFormat) { + this.outputFormat = this.edits.toFormat; + } else if (outputFormat) { + this.outputFormat = outputFormat; + } + + // Fix quality for Thumbor and Custom request type if outputFormat is different from quality type. + if (this.outputFormat) { + const requestType = ['Custom', 'Thumbor']; + const acceptedValues = ['jpeg', 'png', 'webp', 'tiff', 'heif']; + + this.ContentType = `image/${this.outputFormat}`; + if (requestType.includes(this.requestType) && acceptedValues.includes(this.outputFormat)) { + let qualityKey = Object.keys(this.edits).filter(key => acceptedValues.includes(key))[0]; + if (qualityKey && (qualityKey !== this.outputFormat)) { + const qualityValue = this.edits[qualityKey]; + this.edits[this.outputFormat] = qualityValue; + delete this.edits[qualityKey]; + } + } + } + + return Promise.resolve(this); + + } catch (err) { + return Promise.reject(err); + } + } + + /** + * Gets the original image from an Amazon S3 bucket. + * @param {String} bucket - The name of the bucket containing the image. + * @param {String} key - The key name corresponding to the image. + * @return {Promise} - The original image or an error. + */ + async getOriginalImage(bucket, key) { + const S3 = require('aws-sdk/clients/s3'); + const s3 = new S3(); + const imageLocation = { Bucket: bucket, Key: key }; + try { + const originalImage = await s3.getObject(imageLocation).promise(); + + if (originalImage.ContentType) { + this.ContentType = originalImage.ContentType; + } else { + this.ContentType = "image"; + } + + if (originalImage.Expires) { + this.Expires = new Date(originalImage.Expires).toUTCString(); + } + + if (originalImage.LastModified) { + this.LastModified = new Date(originalImage.LastModified).toUTCString(); + } + + if (originalImage.CacheControl) { + this.CacheControl = originalImage.CacheControl; + } else { + this.CacheControl = "max-age=31536000,public"; + } + + return Promise.resolve(originalImage.Body); + } catch(err) { + return Promise.reject({ + status: ('NoSuchKey' === err.code) ? 404 : 500, + code: err.code, + message: err.message + }); + } + } + + /** + * Parses the name of the appropriate Amazon S3 bucket to source the + * original image from. + * @param {String} event - Lambda request body. + * @param {String} requestType - Image handler request type. + */ + parseImageBucket(event, requestType) { + if (requestType === "Default") { + // Decode the image request + const decoded = this.decodeRequest(event); + if (decoded.bucket !== undefined) { + // Check the provided bucket against the whitelist + const sourceBuckets = this.getAllowedSourceBuckets(); + if (sourceBuckets.includes(decoded.bucket) || decoded.bucket.match(new RegExp('^' + sourceBuckets[0] + '$'))) { + return decoded.bucket; + } else { + throw ({ + status: 403, + code: 'ImageBucket::CannotAccessBucket', + message: 'The bucket you specified could not be accessed. Please check that the bucket is specified in your SOURCE_BUCKETS.' + }); + } + } else { + // Try to use the default image source bucket env var + const sourceBuckets = this.getAllowedSourceBuckets(); + return sourceBuckets[0]; + } + } else if (requestType === "Thumbor" || requestType === "Custom") { + // Use the default image source bucket env var + const sourceBuckets = this.getAllowedSourceBuckets(); + return sourceBuckets[0]; + } else { + throw ({ + status: 404, + code: 'ImageBucket::CannotFindBucket', + message: 'The bucket you specified could not be found. Please check the spelling of the bucket name in your request.' + }); + } + } + + /** + * Parses the edits to be made to the original image. + * @param {String} event - Lambda request body. + * @param {String} requestType - Image handler request type. + */ + parseImageEdits(event, requestType) { + if (requestType === "Default") { + const decoded = this.decodeRequest(event); + return decoded.edits; + } else if (requestType === "Thumbor") { + const thumborMapping = new ThumborMapping(); + thumborMapping.process(event); + return thumborMapping.edits; + } else if (requestType === "Custom") { + const thumborMapping = new ThumborMapping(); + const parsedPath = thumborMapping.parseCustomPath(event.path); + thumborMapping.process(parsedPath); + return thumborMapping.edits; + } else { + throw ({ + status: 400, + code: 'ImageEdits::CannotParseEdits', + message: 'The edits you provided could not be parsed. Please check the syntax of your request and refer to the documentation for additional guidance.' + }); + } + } + + /** + * Parses the name of the appropriate Amazon S3 key corresponding to the + * original image. + * @param {String} event - Lambda request body. + * @param {String} requestType - Type, either "Default", "Thumbor", or "Custom". + */ + parseImageKey(event, requestType) { + if (requestType === "Default") { + // Decode the image request and return the image key + const decoded = this.decodeRequest(event); + return decoded.key; + } + + if (requestType === "Thumbor" || requestType === "Custom") { + return decodeURIComponent(event["path"].replace(/\d+x\d+\/|filters[:-][^/;]+|\/fit-in\/+|^\/+/g,'').replace(/^\/+/,'')); + } + + // Return an error for all other conditions + throw ({ + status: 404, + code: 'ImageEdits::CannotFindImage', + message: 'The image you specified could not be found. Please check your request syntax as well as the bucket you specified to ensure it exists.' + }); + } + + /** + * Determines how to handle the request being made based on the URL path + * prefix to the image request. Categorizes a request as either "image" + * (uses the Sharp library), "thumbor" (uses Thumbor mapping), or "custom" + * (uses the rewrite function). + * @param {Object} event - Lambda request body. + */ + parseRequestType(event) { + const path = event["path"]; + // ---- + const matchDefault = new RegExp(/^(\/?)([0-9a-zA-Z+\/]{4})*(([0-9a-zA-Z+\/]{2}==)|([0-9a-zA-Z+\/]{3}=))?$/); + const matchThumbor = new RegExp(/^(\/?)((fit-in)?|(filters:.+\(.?\))?|(unsafe)?).*(.+jpg|.+png|.+webp|.+tiff|.+jpeg)$/i); + const matchCustom = new RegExp(/(\/?)(.*)(jpg|png|webp|tiff|jpeg)/i); + const definedEnvironmentVariables = ( + (process.env.REWRITE_MATCH_PATTERN !== "") && + (process.env.REWRITE_SUBSTITUTION !== "") && + (process.env.REWRITE_MATCH_PATTERN !== undefined) && + (process.env.REWRITE_SUBSTITUTION !== undefined) + ); + // ---- + console.log(path); + if (matchDefault.test(path)) { // use sharp + return 'Default'; + } else if (matchCustom.test(path) && definedEnvironmentVariables) { // use rewrite function then thumbor mappings + return 'Custom'; + } else if (matchThumbor.test(path)) { // use thumbor mappings + return 'Thumbor'; + } else { + throw { + status: 400, + code: 'RequestTypeError', + message: 'The type of request you are making could not be processed. Please ensure that your original image is of a supported file type (jpg, png, tiff, webp) and that your image request is provided in the correct syntax. Refer to the documentation for additional guidance on forming image requests.' + }; + } + } + + /** + * Decodes the base64-encoded image request path associated with default + * image requests. Provides error handling for invalid or undefined path values. + * @param {Object} event - The proxied request object. + */ + decodeRequest(event) { + const path = event["path"]; + if (path !== undefined) { + const splitPath = path.split("/"); + const encoded = splitPath[splitPath.length - 1]; + const toBuffer = Buffer.from(encoded, 'base64'); + try { + // To support European characters, 'ascii' was removed. + return JSON.parse(toBuffer.toString()); + } catch (e) { + throw ({ + status: 400, + code: 'DecodeRequest::CannotDecodeRequest', + message: 'The image request you provided could not be decoded. Please check that your request is base64 encoded properly and refer to the documentation for additional guidance.' + }); + } + } else { + throw ({ + status: 400, + code: 'DecodeRequest::CannotReadPath', + message: 'The URL path you provided could not be read. Please ensure that it is properly formed according to the solution documentation.' + }); + } + } + + /** + * Returns a formatted image source bucket whitelist as specified in the + * SOURCE_BUCKETS environment variable of the image handler Lambda + * function. Provides error handling for missing/invalid values. + */ + getAllowedSourceBuckets() { + const sourceBuckets = process.env.SOURCE_BUCKETS; + if (sourceBuckets === undefined) { + throw ({ + status: 400, + code: 'GetAllowedSourceBuckets::NoSourceBuckets', + message: 'The SOURCE_BUCKETS variable could not be read. Please check that it is not empty and contains at least one source bucket, or multiple buckets separated by commas. Spaces can be provided between commas and bucket names, these will be automatically parsed out when decoding.' + }); + } else { + const formatted = sourceBuckets.replace(/\s+/g, ''); + const buckets = formatted.split(','); + return buckets; + } + } + + /** + * Return the output format depending on the accepts headers and request type + * @param {Object} event - The request body. + */ + getOutputFormat(event) { + const autoWebP = process.env.AUTO_WEBP; + if (autoWebP && event.headers.Accept && event.headers.Accept.includes('image/webp')) { + return 'webp'; + } else if (this.requestType === 'Default') { + const decoded = this.decodeRequest(event); + return decoded.outputFormat; + } + + return null; + } +} + +// Exports +module.exports = ImageRequest; \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/index.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/index.js new file mode 100755 index 000000000..1f1ca8c08 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/index.js @@ -0,0 +1,65 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const ImageRequest = require('./image-request.js'); +const ImageHandler = require('./image-handler.js'); + +exports.handler = async (event) => { + const imageRequest = new ImageRequest(); + const imageHandler = new ImageHandler(); + try { + const request = await imageRequest.setup(event); + const processedRequest = await imageHandler.process(request); + + const headers = getResponseHeaders(); + headers["Content-Type"] = request.ContentType; + headers["Expires"] = request.Expires; + headers["Last-Modified"] = request.LastModified; + headers["Cache-Control"] = request.CacheControl; + + return { + "statusCode": 200, + "headers" : headers, + "body": processedRequest, + "isBase64Encoded": true + }; + } catch (err) { + return { + "statusCode": err.status, + "headers" : getResponseHeaders(true), + "body": JSON.stringify(err), + "isBase64Encoded": false + }; + } +} + +/** + * Generates the appropriate set of response headers based on a success + * or error condition. + * @param {boolean} isErr - has an error been thrown? + */ +const getResponseHeaders = (isErr) => { + const corsEnabled = (process.env.CORS_ENABLED === "Yes"); + const headers = { + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + "Access-Control-Allow-Credentials": true + } + if (corsEnabled) { + headers["Access-Control-Allow-Origin"] = process.env.CORS_ORIGIN; + } + if (isErr) { + headers["Content-Type"] = "application/json" + } + return headers; +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/package.json b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/package.json new file mode 100755 index 000000000..290ed7b85 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/package.json @@ -0,0 +1,31 @@ +{ + "name": "image-handler", + "description": "A Lambda function for performing on-demand image edits and manipulations.", + "main": "index.js", + "author": { + "name": "aws-solutions-builder" + }, + "version": "0.0.1", + "private": true, + "dependencies": { + "sharp": "^0.23.4", + "color": "3.1.2", + "color-name": "1.1.4" + }, + "devDependencies": { + "aws-sdk": "^2.437.0", + "aws-sdk-mock": "^4.4.0", + "mocha": "^6.1.4", + "sinon": "^7.3.2", + "nyc": "^14.0.0" + }, + "scripts": { + "pretest": "npm run build:init && npm install", + "test": "nyc --reporter=html --reporter=text mocha", + "build:init": "rm -rf package-lock.json && rm -rf lambda_dist && rm -rf node_modules", + "build:zip": "zip -rq image-handler.zip .", + "build:dist": "mkdir lambda_dist && mv image-handler.zip lambda_dist/", + "build": "npm run build:init && npm install --arch=x64 --platform=linux --production && npm run build:zip && npm run build:dist" + }, + "license": "Apache-2.0" +} diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-handler.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-handler.js new file mode 100755 index 000000000..a42c2a918 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-handler.js @@ -0,0 +1,476 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const ImageHandler = require('../image-handler'); +const sharp = require('sharp'); +let assert = require('assert'); + +// ---------------------------------------------------------------------------- +// [async] process() +// ---------------------------------------------------------------------------- +describe('process()', function() { + describe('001/default', function() { + it(`Should pass if the output image is different from the input image with edits applied`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon S3 stub + const S3 = require('aws-sdk/clients/s3'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.returns({ + promise: () => { return { + Body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + }} + }) + // ---- + const request = { + requestType: "default", + bucket: "sample-bucket", + key: "sample-image-001.jpg", + edits: { + grayscale: true, + flip: true + }, + originalImage: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + } + // Act + const imageHandler = new ImageHandler(); + const result = await imageHandler.process(request); + // Assert + assert.deepEqual((request.originalImage !== result), true); + }); + }); + describe('002/withToFormat', function() { + it(`Should pass if the output image is in a different format than the original image`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon S3 stub + const S3 = require('aws-sdk/clients/s3'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.returns({ + promise: () => { return { + Body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + }} + }) + // ---- + const request = { + requestType: "default", + bucket: "sample-bucket", + key: "sample-image-001.jpg", + outputFormat: "png", + edits: { + grayscale: true, + flip: true + }, + originalImage: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + } + // Act + const imageHandler = new ImageHandler(); + const result = await imageHandler.process(request); + // Assert + assert.deepEqual((request.originalImage !== result), true); + }); + }); + describe('003/noEditsSpecified', function() { + it(`Should pass if no edits are specified and the original image is returned`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon S3 stub + const S3 = require('aws-sdk/clients/s3'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.returns({ + promise: () => { return { + Body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + }} + }) + // ---- + const request = { + requestType: "default", + bucket: "sample-bucket", + key: "sample-image-001.jpg", + originalImage: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + } + // Act + const imageHandler = new ImageHandler(); + const result = await imageHandler.process(request); + // Assert + assert.deepEqual(result, 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// [async] applyEdits() +// ---------------------------------------------------------------------------- +describe('applyEdits()', function() { + describe('001/standardEdits', function() { + it(`Should pass if a series of standard edits are provided to the + function`, async function() { + // Arrange + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + grayscale: true, + flip: true + } + // Act + const imageHandler = new ImageHandler(); + const result = await imageHandler.applyEdits(originalImage, edits); + // Assert + const expectedResult1 = (result.options.greyscale); + const expectedResult2 = (result.options.flip); + const combinedResults = (expectedResult1 && expectedResult2); + assert.deepEqual(combinedResults, true); + }); + }); + describe('002/overlay', function() { + it(`Should pass if an edit with the overlayWith keyname is passed to + the function`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon S3 stub + const S3 = require('aws-sdk/clients/s3'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.returns({ + promise: () => { return { + Body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + }} + }) + // Act + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + overlayWith: { + bucket: 'aaa', + key: 'bbb' + } + } + // Assert + const imageHandler = new ImageHandler(); + await imageHandler.applyEdits(originalImage, edits).then((result) => { + assert.deepEqual(result.options.input.buffer, originalImage); + }); + }); + }); + describe('003/smartCrop', function() { + it(`Should pass if an edit with the smartCrop keyname is passed to + the function`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon Rekognition stub + const rekognition = require('aws-sdk/clients/rekognition'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + detectFaces.returns({ + promise: () => { return { + FaceDetails: [{ + BoundingBox: { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + } + }] + }} + }) + // Act + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + smartCrop: { + faceIndex: 0, + padding: 0 + } + } + // Assert + const imageHandler = new ImageHandler(); + await imageHandler.applyEdits(originalImage, edits).then((result) => { + //console.log(result); + const sharp = require('sharp'); + const originalImageData = sharp(originalImage); + assert.deepEqual((originalImageData.options.input !== result.options.input), true) + }).catch((err) => { + console.log(err) + }) + }); + }); + describe('004/smartCrop/paddingOutOfBoundsError', function() { + it(`Should pass if an excessive padding value is passed to the + smartCrop filter`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon Rekognition stub + const rekognition = require('aws-sdk/clients/rekognition'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + detectFaces.returns({ + promise: () => { return { + FaceDetails: [{ + BoundingBox: { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + } + }] + }} + }) + // Act + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + smartCrop: { + faceIndex: 0, + padding: 80 + } + } + // Assert + const imageHandler = new ImageHandler(); + await imageHandler.applyEdits(originalImage, edits).then((result) => { + //console.log(result); + const sharp = require('sharp'); + const originalImageData = sharp(originalImage); + assert.deepEqual((originalImageData.options.input !== result.options.input), true) + }).catch((err) => { + console.log(err) + }) + }); + }); + describe('005/smartCrop/boundingBoxError', function() { + it(`Should pass if an excessive faceIndex value is passed to the + smartCrop filter`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon Rekognition stub + const rekognition = require('aws-sdk/clients/rekognition'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + detectFaces.returns({ + promise: () => { return { + FaceDetails: [{ + BoundingBox: { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + } + }] + }} + }) + // Act + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + smartCrop: { + faceIndex: 10, + padding: 0 + } + } + // Assert + const imageHandler = new ImageHandler(); + await imageHandler.applyEdits(originalImage, edits).then((result) => { + //console.log(result); + const sharp = require('sharp'); + const originalImageData = sharp(originalImage); + assert.deepEqual((originalImageData.options.input !== result.options.input), true) + }).catch((err) => { + console.log(err) + }) + }); + }); + describe('006/smartCrop/faceIndexUndefined', function() { + it(`Should pass if a faceIndex value of undefined is passed to the + smartCrop filter`, async function() { + // Arrange + const sinon = require('sinon'); + // ---- Amazon Rekognition stub + const rekognition = require('aws-sdk/clients/rekognition'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + detectFaces.returns({ + promise: () => { return { + FaceDetails: [{ + BoundingBox: { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + } + }] + }} + }) + // Act + const originalImage = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'); + const edits = { + smartCrop: true + } + // Assert + const imageHandler = new ImageHandler(); + await imageHandler.applyEdits(originalImage, edits).then((result) => { + //console.log(result); + const sharp = require('sharp'); + const originalImageData = sharp(originalImage); + assert.deepEqual((originalImageData.options.input !== result.options.input), true) + }).catch((err) => { + console.log(err) + }) + }); + }); +}); + +// ---------------------------------------------------------------------------- +// [async] getOverlayImage() +// ---------------------------------------------------------------------------- +describe('getOverlayImage()', function() { + describe('001/validParameters', function() { + it(`Should pass if the proper bucket name and key are supplied, + simulating an image file that can be retrieved`, async function() { + // Arrange + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'validBucket', Key: 'validKey'}).returns({ + promise: () => { return { + Body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64') + }} + }) + // Act + const imageHandler = new ImageHandler(); + const metadata = await sharp(Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64')).metadata(); + const result = await imageHandler.getOverlayImage('validBucket', 'validKey', '100', '100', '20', metadata); + // Assert + assert.deepEqual(result, Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAsSAAALEgHS3X78AAAADUlEQVQI12P4z8CQCgAEZgFlTg0nBwAAAABJRU5ErkJggg==', 'base64')); + }); + }); + describe('002/imageDoesNotExist', async function() { + it(`Should throw an error if an invalid bucket or key name is provided, + simulating a non-existant overlay image`, async function() { + // Arrange + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'invalidBucket', Key: 'invalidKey'}).returns({ + promise: () => { + return Promise.reject({ + code: 500, + message: 'SimulatedInvalidParameterException' + }) + } + }); + // Act + const imageHandler = new ImageHandler(); + // Assert + imageHandler.getOverlayImage('invalidBucket', 'invalidKey').then((result) => { + assert.equal(typeof result, Error); + }).catch((err) => { + console.log(err) + }) + }); + }); +}); + +// ---------------------------------------------------------------------------- +// [async] getCropArea() +// ---------------------------------------------------------------------------- +describe('getCropArea()', function() { + describe('001/validParameters', function() { + it(`Should pass if the crop area can be calculated using a series of + valid inputs/parameters`, function() { + // Arrange + const boundingBox = { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + }; + const options = { padding: 20 }; + const metadata = { + width: 200, + height: 400 + }; + // Act + const imageHandler = new ImageHandler(); + const result = imageHandler.getCropArea(boundingBox, options, metadata); + // Assert + const expectedResult = { + left: 90, + top: 112, + width: 86, + height: 112 + } + assert.deepEqual(result, expectedResult); + }); + }); +}); + + +// ---------------------------------------------------------------------------- +// [async] getBoundingBox() +// ---------------------------------------------------------------------------- +describe('getBoundingBox()', function() { + describe('001/validParameters', function() { + it(`Should pass if the proper parameters are passed to the function`, + async function() { + // Arrange + const sinon = require('sinon'); + const rekognition = require('aws-sdk/clients/rekognition'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + // ---- + const imageBytes = Buffer.from('TestImageData'); + detectFaces.withArgs({Image: {Bytes: imageBytes}}).returns({ + promise: () => { return { + FaceDetails: [{ + BoundingBox: { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + } + }] + }} + }) + // ---- + const currentImage = imageBytes; + const faceIndex = 0; + // Act + const imageHandler = new ImageHandler(); + const result = await imageHandler.getBoundingBox(currentImage, faceIndex); + // Assert + const expectedResult = { + Height: 0.18, + Left: 0.55, + Top: 0.33, + Width: 0.23 + }; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/errorHandling', function() { + it(`Should simulate an error condition returned by Rekognition`, + async function() { + // Arrange + const rekognition = require('aws-sdk/clients/rekognition'); + const sinon = require('sinon'); + const detectFaces = rekognition.prototype.detectFaces = sinon.stub(); + detectFaces.returns({ + promise: () => { + return Promise.reject({ + code: 500, + message: 'SimulatedError' + }) + } + }) + // ---- + const currentImage = Buffer.from('NotTestImageData'); + const faceIndex = 0; + // Act + const imageHandler = new ImageHandler(); + // Assert + imageHandler.getBoundingBox(currentImage, faceIndex).then((result) => { + assert.equal(typeof result, Error); + }).catch((err) => { + console.log(err) + }) + }); + }); +}); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-request.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-request.js new file mode 100755 index 000000000..9507dacef --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-image-request.js @@ -0,0 +1,757 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const ImageRequest = require('../image-request'); +let assert = require('assert'); + +// ---------------------------------------------------------------------------- +// [async] setup() +// ---------------------------------------------------------------------------- +describe('setup()', function() { + describe('001/defaultImageRequest', function() { + it(`Should pass when a default image request is provided and populate + the ImageRequest object with the proper values`, async function() { + // Arrange + const event = { + path : '/eyJidWNrZXQiOiJ2YWxpZEJ1Y2tldCIsImtleSI6InZhbGlkS2V5IiwiZWRpdHMiOnsiZ3JheXNjYWxlIjp0cnVlfSwib3V0cHV0Rm9ybWF0IjoianBlZyJ9' + } + process.env = { + SOURCE_BUCKETS : "validBucket, validBucket2" + } + // ---- + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'validBucket', Key: 'validKey'}).returns({ + promise: () => { return { + Body: Buffer.from('SampleImageContent\n') + }} + }) + // Act + const imageRequest = new ImageRequest(); + await imageRequest.setup(event); + const expectedResult = { + requestType: 'Default', + bucket: 'validBucket', + key: 'validKey', + edits: { grayscale: true }, + outputFormat: 'jpeg', + originalImage: Buffer.from('SampleImageContent\n'), + CacheControl: 'max-age=31536000,public', + ContentType: 'image/jpeg' + } + // Assert + assert.deepEqual(imageRequest, expectedResult); + }); + }); + describe('002/thumborImageRequest', function() { + it(`Should pass when a thumbor image request is provided and populate + the ImageRequest object with the proper values`, async function() { + // Arrange + const event = { + path : "/filters:grayscale()/test-image-001.jpg" + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // ---- + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'allowedBucket001', Key: 'test-image-001.jpg'}).returns({ + promise: () => { return { + Body: Buffer.from('SampleImageContent\n') + }} + }) + // Act + const imageRequest = new ImageRequest(); + await imageRequest.setup(event); + const expectedResult = { + requestType: 'Thumbor', + bucket: 'allowedBucket001', + key: 'test-image-001.jpg', + edits: { grayscale: true }, + originalImage: Buffer.from('SampleImageContent\n'), + CacheControl: 'max-age=31536000,public', + ContentType: 'image' + } + // Assert + assert.deepEqual(imageRequest, expectedResult); + }); + }); + describe('003/customImageRequest', function() { + it(`Should pass when a custom image request is provided and populate + the ImageRequest object with the proper values`, async function() { + // Arrange + const event = { + path : '/filters-rotate(90)/filters-grayscale()/custom-image.jpg' + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002", + REWRITE_MATCH_PATTERN: /(filters-)/gm, + REWRITE_SUBSTITUTION: 'filters:' + } + // ---- + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'allowedBucket001', Key: 'custom-image.jpg'}).returns({ + promise: () => { return { + CacheControl: 'max-age=300,public', + ContentType: 'custom-type', + Expires: 'Tue, 24 Dec 2019 13:46:28 GMT', + LastModified: 'Sat, 19 Dec 2009 16:30:47 GMT', + Body: Buffer.from('SampleImageContent\n') + }} + }) + // Act + const imageRequest = new ImageRequest(); + await imageRequest.setup(event); + const expectedResult = { + requestType: 'Custom', + bucket: 'allowedBucket001', + key: 'custom-image.jpg', + edits: { + grayscale: true, + rotate: 90 + }, + originalImage: Buffer.from('SampleImageContent\n'), + CacheControl: 'max-age=300,public', + ContentType: 'custom-type', + Expires: 'Tue, 24 Dec 2019 13:46:28 GMT', + LastModified: 'Sat, 19 Dec 2009 16:30:47 GMT', + } + // Assert + assert.deepEqual(imageRequest, expectedResult); + }); + }); + describe('004/errorCase', function() { + it(`Should pass when an error is caught`, async function() { + // Assert + const event = { + path : '/eyJidWNrZXQiOiJ2YWxpZEJ1Y2tldCIsImtleSI6InZhbGlkS2V5IiwiZWRpdHMiOnsiZ3JheXNjYWxlIjp0cnVlfX0=' + } + // ---- + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'validBucket', Key: 'validKey'}).returns({ + promise: () => { return { + Body: Buffer.from('SampleImageContent\n') + }} + }) + // Act + const imageRequest = new ImageRequest(); + // Assert + await imageRequest.setup(event).then(() => { + console.log(data); + }).catch((err) => { + console.log(err); + assert.deepEqual(err.code, 'ImageBucket::CannotAccessBucket'); + }) + }); + }); +}); +// ---------------------------------------------------------------------------- +// getOriginalImage() +// ---------------------------------------------------------------------------- +describe('getOriginalImage()', function() { + describe('001/imageExists', function() { + it(`Should pass if the proper bucket name and key are supplied, + simulating an image file that can be retrieved`, async function() { + // Arrange + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'validBucket', Key: 'validKey'}).returns({ + promise: () => { return { + Body: Buffer.from('SampleImageContent\n') + }} + }) + // Act + const imageRequest = new ImageRequest(); + const result = await imageRequest.getOriginalImage('validBucket', 'validKey'); + // Assert + assert.deepEqual(result, Buffer.from('SampleImageContent\n')); + }); + }); + describe('002/imageDoesNotExist', async function() { + it(`Should throw an error if an invalid bucket or key name is provided, + simulating a non-existant original image`, async function() { + // Arrange + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'invalidBucket', Key: 'invalidKey'}).returns({ + promise: () => { + return Promise.reject({ + code: 'NoSuchKey', + message: 'SimulatedException' + }) + } + }); + // Act + const imageRequest = new ImageRequest(); + // Assert + imageRequest.getOriginalImage('invalidBucket', 'invalidKey').then((result) => { + assert.equal(typeof result, Error); + assert.equal(result.status, 404); + }).catch((err) => console.log(err)); + }); + }); + describe('003/unknownError', async function() { + it(`Should throw an error if an unkown problem happens when getting an object`, async function() { + // Arrange + const S3 = require('aws-sdk/clients/s3'); + const sinon = require('sinon'); + const getObject = S3.prototype.getObject = sinon.stub(); + getObject.withArgs({Bucket: 'invalidBucket', Key: 'invalidKey'}).returns({ + promise: () => { + return Promise.reject({ + code: 'InternalServerError', + message: 'SimulatedException' + }) + } + }); + // Act + const imageRequest = new ImageRequest(); + // Assert + imageRequest.getOriginalImage('invalidBucket', 'invalidKey').then((result) => { + assert.equal(typeof result, Error); + assert.equal(result.status, 500); + }).catch((err) => console.log(err)); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// parseImageBucket() +// ---------------------------------------------------------------------------- +describe('parseImageBucket()', function() { + describe('001/defaultRequestType/bucketSpecifiedInRequest/allowed', function() { + it(`Should pass if the bucket name is provided in the image request + and has been whitelisted in SOURCE_BUCKETS`, function() { + // Arrange + const event = { + path : '/eyJidWNrZXQiOiJhbGxvd2VkQnVja2V0MDAxIiwia2V5Ijoic2FtcGxlSW1hZ2VLZXkwMDEuanBnIiwiZWRpdHMiOnsiZ3JheXNjYWxlIjoidHJ1ZSJ9fQ==' + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageBucket(event, 'Default'); + // Assert + const expectedResult = 'allowedBucket001'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/defaultRequestType/bucketSpecifiedInRequest/notAllowed', function() { + it(`Should throw an error if the bucket name is provided in the image request + but has not been whitelisted in SOURCE_BUCKETS`, function() { + // Arrange + const event = { + path : '/eyJidWNrZXQiOiJhbGxvd2VkQnVja2V0MDAxIiwia2V5Ijoic2FtcGxlSW1hZ2VLZXkwMDEuanBnIiwiZWRpdHMiOnsiZ3JheXNjYWxlIjoidHJ1ZSJ9fQ==' + } + process.env = { + SOURCE_BUCKETS : "allowedBucket003, allowedBucket004" + } + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.parseImageBucket(event, 'Default'); + }, Object, { + status: 403, + code: 'ImageBucket::CannotAccessBucket', + message: 'The bucket you specified could not be accessed. Please check that the bucket is specified in your SOURCE_BUCKETS.' + }); + }); + }); + describe('003/defaultRequestType/bucketNotSpecifiedInRequest', function() { + it(`Should pass if the image request does not contain a source bucket + but SOURCE_BUCKETS contains at least one bucket that can be + used as a default`, function() { + // Arrange + const event = { + path : '/eyJrZXkiOiJzYW1wbGVJbWFnZUtleTAwMS5qcGciLCJlZGl0cyI6eyJncmF5c2NhbGUiOiJ0cnVlIn19==' + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageBucket(event, 'Default'); + // Assert + const expectedResult = 'allowedBucket001'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('004/thumborRequestType', function() { + it(`Should pass if there is at least one SOURCE_BUCKET specified that can + be used as the default for Thumbor requests`, function() { + // Arrange + const event = { + path : "/filters:grayscale()/test-image-001.jpg" + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageBucket(event, 'Thumbor'); + // Assert + const expectedResult = 'allowedBucket001'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('005/customRequestType', function() { + it(`Should pass if there is at least one SOURCE_BUCKET specified that can + be used as the default for Custom requests`, function() { + // Arrange + const event = { + path : "/filters:grayscale()/test-image-001.jpg" + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageBucket(event, 'Custom'); + // Assert + const expectedResult = 'allowedBucket001'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('006/invalidRequestType', function() { + it(`Should pass if there is at least one SOURCE_BUCKET specified that can + be used as the default for Custom requests`, function() { + // Arrange + const event = { + path : "/filters:grayscale()/test-image-001.jpg" + } + process.env = { + SOURCE_BUCKETS : "allowedBucket001, allowedBucket002" + } + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.parseImageBucket(event, undefined); + }, Object, { + status: 400, + code: 'ImageBucket::CannotFindBucket', + message: 'The bucket you specified could not be found. Please check the spelling of the bucket name in your request.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// parseImageEdits() +// ---------------------------------------------------------------------------- +describe('parseImageEdits()', function() { + describe('001/defaultRequestType', function() { + it(`Should pass if the proper result is returned for a sample base64- + encoded image request`, function() { + // Arrange + const event = { + path : '/eyJlZGl0cyI6eyJncmF5c2NhbGUiOiJ0cnVlIiwicm90YXRlIjo5MCwiZmxpcCI6InRydWUifX0=' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageEdits(event, 'Default'); + // Assert + const expectedResult = { + grayscale: 'true', + rotate: 90, + flip: 'true' + } + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/thumborRequestType', function() { + it(`Should pass if the proper result is returned for a sample thumbor- + type image request`, function() { + // Arrange + const event = { + path : '/filters:rotate(90)/filters:grayscale()/thumbor-image.jpg' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageEdits(event, 'Thumbor'); + // Assert + const expectedResult = { + rotate: 90, + grayscale: true + } + assert.deepEqual(result, expectedResult); + }); + }); + describe('003/customRequestType', function() { + it(`Should pass if the proper result is returned for a sample custom- + type image request`, function() { + // Arrange + const event = { + path : '/filters-rotate(90)/filters-grayscale()/thumbor-image.jpg' + } + process.env.REWRITE_MATCH_PATTERN = /(filters-)/gm; + process.env.REWRITE_SUBSTITUTION = 'filters:'; + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageEdits(event, 'Custom'); + // Assert + const expectedResult = { + rotate: 90, + grayscale: true + } + assert.deepEqual((typeof result !== undefined), !undefined) + }); + }); + describe('004/customRequestType', function() { + it(`Should throw an error if a requestType is not specified and/or the image edits + cannot be parsed`, function() { + // Arrange + const event = { + path : '/filters:rotate(90)/filters:grayscale()/other-image.jpg' + } + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.parseImageEdits(event, undefined); + }, Object, { + status: 400, + code: 'ImageEdits::CannotParseEdits', + message: 'The edits you provided could not be parsed. Please check the syntax of your request and refer to the documentation for additional guidance.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// parseImageKey() +// ---------------------------------------------------------------------------- +describe('parseImageKey()', function() { + describe('001/defaultRequestType', function() { + it(`Should pass if an image key value is provided in the default + request format`, function() { + // Arrange + const event = { + path : '/eyJidWNrZXQiOiJteS1zYW1wbGUtYnVja2V0Iiwia2V5Ijoic2FtcGxlLWltYWdlLTAwMS5qcGcifQ==' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageKey(event, 'Default'); + // Assert + const expectedResult = 'sample-image-001.jpg'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/thumborRequestType', function() { + it(`Should pass if an image key value is provided in the thumbor + request format`, function() { + // Arrange + const event = { + path : '/filters:rotate(90)/filters:grayscale()/thumbor-image.jpg' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageKey(event, 'Thumbor'); + // Assert + const expectedResult = 'thumbor-image.jpg'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('003/customRequestType', function() { + it(`Should pass if an image key value is provided in the custom + request format`, function() { + // Arrange + const event = { + path : '/filters-rotate(90)/filters-grayscale()/custom-image.jpg' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseImageKey(event, 'Custom'); + // Assert + const expectedResult = 'custom-image.jpg'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('004/elseCondition', function() { + it(`Should throw an error if an unrecognized requestType is passed into the + function as a parameter`, function() { + // Arrange + const event = { + path : '/filters:rotate(90)/filters:grayscale()/other-image.jpg' + } + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.parseImageKey(event, undefined); + }, Object, { + status: 400, + code: 'ImageEdits::CannotFindImage', + message: 'The image you specified could not be found. Please check your request syntax as well as the bucket you specified to ensure it exists.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// parseRequestType() +// ---------------------------------------------------------------------------- +describe('parseRequestType()', function() { + describe('001/defaultRequestType', function() { + it(`Should pass if the method detects a default request`, function() { + // Arrange + const event = { + path: '/eyJidWNrZXQiOiJteS1zYW1wbGUtYnVja2V0Iiwia2V5IjoibXktc2FtcGxlLWtleSIsImVkaXRzIjp7ImdyYXlzY2FsZSI6dHJ1ZX19' + } + process.env = {}; + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseRequestType(event); + // Assert + const expectedResult = 'Default'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/thumborRequestType', function() { + it(`Should pass if the method detects a thumbor request`, function() { + // Arrange + const event = { + path: '/unsafe/filters:brightness(10):contrast(30)/https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Coffee_berries_1.jpg/1200px-Coffee_berries_1.jpg' + } + process.env = {}; + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseRequestType(event); + // Assert + const expectedResult = 'Thumbor'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('003/customRequestType', function() { + it(`Should pass if the method detects a custom request`, function() { + // Arrange + const event = { + path: '/additionalImageRequestParameters/image.jpg' + } + process.env = { + REWRITE_MATCH_PATTERN: 'matchPattern', + REWRITE_SUBSTITUTION: 'substitutionString' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.parseRequestType(event); + // Assert + const expectedResult = 'Custom'; + assert.deepEqual(result, expectedResult); + }); + }); + describe('004/elseCondition', function() { + it(`Should throw an error if the method cannot determine the request + type based on the three groups given`, function() { + // Arrange + const event = { + path : '12x12e24d234r2ewxsad123d34r' + } + process.env = {}; + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + const a = imageRequest.parseRequestType(event); + }, Object, { + status: 400, + code: 'RequestType::CannotDetermineRequestType', + message: 'The type of request you are making could not be properly routed. Please check your request syntax and refer to the documentation for additional guidance.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// decodeRequest() +// ---------------------------------------------------------------------------- +describe('decodeRequest()', function() { + describe('001/validRequestPathSpecified', function() { + it(`Should pass if a valid base64-encoded path has been specified`, + function() { + // Arrange + const event = { + path : '/eyJidWNrZXQiOiJidWNrZXQtbmFtZS1oZXJlIiwia2V5Ijoia2V5LW5hbWUtaGVyZSJ9' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.decodeRequest(event); + // Assert + const expectedResult = { + bucket: 'bucket-name-here', + key: 'key-name-here' + }; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/invalidRequestPathSpecified', function() { + it(`Should throw an error if a valid base64-encoded path has not been specified`, + function() { + // Arrange + const event = { + path : '/someNonBase64EncodedContentHere' + } + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.decodeRequest(event); + }, Object, { + status: 400, + code: 'DecodeRequest::CannotDecodeRequest', + message: 'The image request you provided could not be decoded. Please check that your request is base64 encoded properly and refer to the documentation for additional guidance.' + }); + }); + }); + describe('003/noPathSpecified', function() { + it(`Should throw an error if no path is specified at all`, + function() { + // Arrange + const event = {} + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.decodeRequest(event); + }, Object, { + status: 400, + code: 'DecodeRequest::CannotReadPath', + message: 'The URL path you provided could not be read. Please ensure that it is properly formed according to the solution documentation.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// getAllowedSourceBuckets() +// ---------------------------------------------------------------------------- +describe('getAllowedSourceBuckets()', function() { + describe('001/sourceBucketsSpecified', function() { + it(`Should pass if the SOURCE_BUCKETS environment variable is not empty + and contains valid inputs`, function() { + // Arrange + process.env = { + SOURCE_BUCKETS: 'allowedBucket001, allowedBucket002' + } + // Act + const imageRequest = new ImageRequest(); + const result = imageRequest.getAllowedSourceBuckets(); + // Assert + const expectedResult = ['allowedBucket001', 'allowedBucket002']; + assert.deepEqual(result, expectedResult); + }); + }); + describe('002/noSourceBucketsSpecified', function() { + it(`Should throw an error if the SOURCE_BUCKETS environment variable is + empty or does not contain valid values`, function() { + // Arrange + process.env = {}; + // Act + const imageRequest = new ImageRequest(); + // Assert + assert.throws(function() { + imageRequest.getAllowedSourceBuckets(); + }, Object, { + status: 400, + code: 'GetAllowedSourceBuckets::NoSourceBuckets', + message: 'The SOURCE_BUCKETS variable could not be read. Please check that it is not empty and contains at least one source bucket, or multiple buckets separated by commas. Spaces can be provided between commas and bucket names, these will be automatically parsed out when decoding.' + }); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// getOutputFormat() +// ---------------------------------------------------------------------------- +describe('getOutputFormat()', function () { + describe('001/AcceptsHeaderIncludesWebP', function () { + it(`Should pass if it returns "webp" for an accepts header which includes webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: true + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, 'webp'); + }); + }); + describe('002/AcceptsHeaderDoesNotIncludeWebP', function () { + it(`Should pass if it returns null for an accepts header which does not include webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: true + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); + describe('003/AutoWebPDisabled', function () { + it(`Should pass if it returns null when AUTO_WEBP is disabled with accepts header including webp`, function () { + // Arrange + process.env = { + AUTO_WEBP: false + }; + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); + describe('004/AutoWebPUnset', function () { + it(`Should pass if it returns null when AUTO_WEBP is not set with accepts header including webp`, function () { + // Arrange + const event = { + headers: { + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + } + }; + // Act + const imageRequest = new ImageRequest(); + var result = imageRequest.getOutputFormat(event); + // Assert + assert.deepEqual(result, null); + }); + }); +}); diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-thumbor-mapping.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-thumbor-mapping.js new file mode 100755 index 000000000..1186bc5d3 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/test/test-thumbor-mapping.js @@ -0,0 +1,842 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const ThumborMapping = require('../thumbor-mapping'); +let assert = require('assert'); + +// ---------------------------------------------------------------------------- +// process() +// ---------------------------------------------------------------------------- +describe('process()', function() { + describe('001/thumborRequest', function() { + it(`Should pass if the proper edit translations are applied and in the + correct order`, function() { + // Arrange + const event = { + path : "/fit-in/200x300/filters:grayscale()/test-image-001.jpg" + } + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.process(event); + // Assert + const expectedResult = { + edits: { + resize: { + width: 200, + height: 300, + fit: 'inside' + }, + grayscale: true + } + }; + assert.deepEqual(thumborMapping.edits, expectedResult.edits); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// parseCustomPath() +// ---------------------------------------------------------------------------- +describe('parseCustomPath()', function() { + describe('001/validPath', function() { + it(`Should pass if the proper edit translations are applied and in the + correct order`, function() { + const event = { + path : '/filters-rotate(90)/filters-grayscale()/thumbor-image.jpg' + } + process.env.REWRITE_MATCH_PATTERN = /(filters-)/gm; + process.env.REWRITE_SUBSTITUTION = 'filters:'; + // Act + const thumborMapping = new ThumborMapping(); + const result = thumborMapping.parseCustomPath(event.path); + // Assert + const expectedResult = '/filters:rotate(90)/filters:grayscale()/thumbor-image.jpg'; + assert.deepEqual(result.path, expectedResult); + }); + }); + describe('002/undefinedEnvironmentVariables', function() { + it(`Should throw an error if the environment variables are left undefined`, function() { + const event = { + path : '/filters-rotate(90)/filters-grayscale()/thumbor-image.jpg' + } + process.env.REWRITE_MATCH_PATTERN = undefined; + process.env.REWRITE_SUBSTITUTION = undefined; + // Act + const thumborMapping = new ThumborMapping(); + // Assert + assert.throws(function() { + thumborMapping.parseCustomPath(event.path); + }, Error, 'ThumborMapping::ParseCustomPath::ParsingError'); + }); + }); + describe('003/undefinedPath', function() { + it(`Should throw an error if the path is not defined`, function() { + const event = {}; + process.env.REWRITE_MATCH_PATTERN = /(filters-)/gm; + process.env.REWRITE_SUBSTITUTION = 'filters:'; + // Act + const thumborMapping = new ThumborMapping(); + // Assert + assert.throws(function() { + thumborMapping.parseCustomPath(event.path); + }, Error, 'ThumborMapping::ParseCustomPath::ParsingError'); + }); + }); + describe('004/undefinedAll', function() { + it(`Should throw an error if the path is not defined`, function() { + const event = {}; + process.env.REWRITE_MATCH_PATTERN = undefined; + process.env.REWRITE_SUBSTITUTION = undefined; + // Act + const thumborMapping = new ThumborMapping(); + // Assert + assert.throws(function() { + thumborMapping.parseCustomPath(event.path); + }, Error, 'ThumborMapping::ParseCustomPath::ParsingError'); + }); + }); +}); + +// ---------------------------------------------------------------------------- +// mapFilter() +// ---------------------------------------------------------------------------- +describe('mapFilter()', function() { + describe('001/autojpg', function() { + it(`Should pass if the filter is successfully converted from + Thumbor:autojpg()`, function() { + // Arrange + const edit = 'filters:autojpg()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { toFormat: 'jpeg' } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('002/background_color', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:background_color()`, function() { + // Arrange + const edit = 'filters:background_color(ffff)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { flatten: { background: {r: 255, g: 255, b: 255}}} + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('003/blur/singleParameter', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:blur()`, function() { + // Arrange + const edit = 'filters:blur(60)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { blur: 30 } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('004/blur/doubleParameter', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:blur()`, function() { + // Arrange + const edit = 'filters:blur(60, 2)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { blur: 2 } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('005/convolution', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:convolution()`, function() { + // Arrange + const edit = 'filters:convolution(1;2;1;2;4;2;1;2;1,3,true)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { convolve: { + width: 3, + height: 3, + kernel: [1,2,1,2,4,2,1,2,1] + }} + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('006/equalize', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:equalize()`, function() { + // Arrange + const edit = 'filters:equalize()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { normalize: 'true' } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('007/fill/resizeUndefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:fill()`, function() { + // Arrange + const edit = 'filters:fill(fff)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { resize: { background: { r: 255, g: 255, b: 255 } }} + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + + describe('008/fill/resizeDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:fill()`, function() { + // Arrange + const edit = 'filters:fill(fff)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { resize: { background: { r: 255, g: 255, b: 255 } }} + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('009/format/supportedFileType', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:format()`, function() { + // Arrange + const edit = 'filters:format(png)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { toFormat: 'png' } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('010/format/unsupportedFileType', function() { + it(`Should return undefined if an accepted file format is not specified` + , function() { + // Arrange + const edit = 'filters:format(test)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('011/no_upscale/resizeUndefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:no_upscale()`, function() { + // Arrange + const edit = 'filters:no_upscale()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { + withoutEnlargement: true + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('012/no_upscale/resizeDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:no_upscale()`, function() { + // Arrange + const edit = 'filters:no_upscale()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = { + height: 400, + width: 300 + }; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { + height: 400, + width: 300, + withoutEnlargement: true + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('013/proportion/resizeDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:proportion()`, function() { + // Arrange + const edit = 'filters:proportion(0.3)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits = { + resize: { + width: 200, + height: 200 + } + }; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { + height: 60, + width: 60 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('014/proportion/resizeUndefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:resize()`, function() { + // Arrange + const edit = 'filters:proportion(0.3)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const actualResult = (typeof(thumborMapping.edits.resize) !== undefined); + const expectedResult = true; + assert.deepEqual(actualResult, expectedResult); + }); + }); + describe('015/quality/jpg', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:quality()`, function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + jpeg: { + quality: 50 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('016/quality/png', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:quality()`, function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'png'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + png: { + quality: 50 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('017/quality/webp', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:quality()`, function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'webp'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + webp: { + quality: 50 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('018/quality/tiff', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:quality()`, function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'tiff'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + tiff: { + quality: 50 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('019/quality/heif', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:quality()`, function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'heif'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + heif: { + quality: 50 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('020/quality/other', function() { + it(`Should return undefined if an unsupported file type is provided`, + function() { + // Arrange + const edit = 'filters:quality(50)'; + const filetype = 'xml'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('021/rgb', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:rgb()`, function() { + // Arrange + const edit = 'filters:rgb(10, 10, 10)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + tint: { + r: 25.5, + g: 25.5, + b: 25.5 + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('022/rotate', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:rotate()`, function() { + // Arrange + const edit = 'filters:rotate(75)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + rotate: 75 + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('023/sharpen', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:sharpen()`, function() { + // Arrange + const edit = 'filters:sharpen(75, 5)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + sharpen: 3.5 + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('024/stretch/default', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:stretch()`, function() { + // Arrange + const edit = 'filters:stretch()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { fit: 'fill' } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('025/stretch/resizeDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:stretch()`, function() { + // Arrange + const edit = 'filters:stretch()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { fit: 'fill' } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('026/stretch/sizingMethodUndefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:stretch()`, function() { + // Arrange + const edit = 'filters:stretch()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.sizingMethod = undefined; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { fit: 'fill' } + }, + sizingMethod: undefined + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('027/stretch/sizingMethodNotFitIn', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:stretch()`, function() { + // Arrange + const edit = 'filters:stretch()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.sizingMethod = "cover"; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { fit: 'fill' } + }, + sizingMethod: "cover" + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('028/stretch/sizingMethodFitIn', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:stretch()`, function() { + // Arrange + const edit = 'filters:stretch()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.sizingMethod = "fit-in"; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: {} + }, + sizingMethod: "fit-in" + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('029/strip_exif', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:strip_exif()`, function() { + // Arrange + const edit = 'filters:strip_exif()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + rotate: 0 + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('030/strip_icc', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:strip_icc()`, function() { + // Arrange + const edit = 'filters:strip_icc()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + rotate: 0 + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('031/upscale', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:upscale()`, function() { + // Arrange + const edit = 'filters:upscale()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { + fit: 'inside' + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('032/upscale/resizeNotUndefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:upscale()`, function() { + // Arrange + const edit = 'filters:upscale()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.edits.resize = {}; + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + resize: { + fit: 'inside' + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('032/watermark/positionDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:watermark()`, function() { + // Arrange + const edit = 'filters:watermark(bucket,key,100,100,0)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + overlayWith: { + bucket: 'bucket', + key: 'key', + alpha: '0', + wRatio: undefined, + hRatio: undefined, + options: { + left: '100', + top: '100' + } + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('033/watermark/positionDefinedByPercentile', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:watermark()`, function() { + // Arrange + const edit = 'filters:watermark(bucket,key,50p,30p,0)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + overlayWith: { + bucket: 'bucket', + key: 'key', + alpha: '0', + wRatio: undefined, + hRatio: undefined, + options: { + left: '50p', + top: '30p' + } + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('034/watermark/positionDefinedWrong', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:watermark()`, function() { + // Arrange + const edit = 'filters:watermark(bucket,key,x,x,0)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + overlayWith: { + bucket: 'bucket', + key: 'key', + alpha: '0', + wRatio: undefined, + hRatio: undefined, + options: {} + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('035/watermark/ratioDefined', function() { + it(`Should pass if the filter is successfully translated from + Thumbor:watermark()`, function() { + // Arrange + const edit = 'filters:watermark(bucket,key,100,100,0,10,10)'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { + edits: { + overlayWith: { + bucket: 'bucket', + key: 'key', + alpha: '0', + wRatio: '10', + hRatio: '10', + options: { + left: '100', + top: '100' + } + } + } + }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); + describe('036/elseCondition', function() { + it(`Should pass if undefined is returned for an unsupported filter`, + function() { + // Arrange + const edit = 'filters:notSupportedFilter()'; + const filetype = 'jpg'; + // Act + const thumborMapping = new ThumborMapping(); + thumborMapping.mapFilter(edit, filetype); + // Assert + const expectedResult = { edits: {} }; + assert.deepEqual(thumborMapping, expectedResult); + }); + }); +}) \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/thumbor-mapping.js b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/thumbor-mapping.js new file mode 100755 index 000000000..4dca04f57 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/lib/lambda/image-handler/thumbor-mapping.js @@ -0,0 +1,256 @@ +/********************************************************************************************************************* + * 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. * + *********************************************************************************************************************/ + +const Color = require('color'); +const ColorName = require('color-name'); + +class ThumborMapping { + + // Constructor + constructor() { + this.edits = {}; + this.sizingMethod; + } + + /** + * Initializer function for creating a new Thumbor mapping, used by the image + * handler to perform image modifications based on legacy URL path requests. + * @param {Object} event - The request body. + */ + process(event) { + // Setup + this.path = event.path; + const edits = this.path.split('/'); + const filetype = (this.path.split('.'))[(this.path.split('.')).length - 1]; + + // Process the Dimensions + const dimPath = this.path.match(/[^\/]\d+x\d+/g); + if (dimPath) { + const dims = dimPath[0].split('x'); + // Set only if the dimensions provided are valid + if (!isNaN(dims[0]) && !isNaN(dims[1])) { + this.edits.resize = {}; + this.edits.resize.fit = 'fill'; + + // Assign dimenions from the first match only to avoid parsing dimension from image file names + this.edits.resize.width = Number(dims[0]); + this.edits.resize.height = Number(dims[1]); + } + } + + // Parse the image path + for (let i = 0; i < edits.length; i++) { + const edit = edits[i]; + if (edit === ('fit-in')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + + this.edits.resize.fit = 'inside'; + this.sizingMethod = edit; + } else if (edit.includes('filters:')) { + this.mapFilter(edit, filetype); + } + } + + return this; + } + + /** + * Enables users to migrate their current image request model to the SIH solution, + * without changing their legacy application code to accomodate new image requests. + * @param {String} path - The URL path extracted from the web request. + */ + parseCustomPath(path) { + // Setup from the environment variables + const matchPattern = process.env.REWRITE_MATCH_PATTERN; + const substitution = process.env.REWRITE_SUBSTITUTION; + // Perform the substitution and return + if (path !== undefined && matchPattern !== undefined && substitution !== undefined) { + const parsedPath = path.replace(matchPattern, substitution); + const output = { path: parsedPath }; + return output; + } else { + throw new Error('ThumborMapping::ParseCustomPath::ParsingError'); + } + } + + /** + * Scanner function for matching supported Thumbor filters and converting their + * capabilities into Sharp.js supported operations. + * @param {String} edit - The URL path filter. + * @param {String} filetype - The file type of the original image. + */ + mapFilter(edit, filetype) { + const matched = edit.match(/:(.+)\((.*)\)/); + const key = matched[1]; + let value = matched[2]; + // Find the proper filter + if (key === ('autojpg')) { + this.edits.toFormat = 'jpeg'; + } + else if (key === ('background_color')) { + if (!ColorName[value]) { + value = `#${value}` + } + this.edits.flatten = { background: Color(value).object() }; + } + else if (key === ('blur')) { + const val = value.split(','); + this.edits.blur = (val.length > 1) ? Number(val[1]) : Number(val[0]) / 2; + } + else if (key === ('convolution')) { + const arr = value.split(','); + const strMatrix = (arr[0]).split(';'); + let matrix = []; + strMatrix.forEach(function(str) { + matrix.push(Number(str)); + }); + const matrixWidth = arr[1]; + let matrixHeight = 0; + let counter = 0; + for (let i = 0; i < matrix.length; i++) { + if (counter === (matrixWidth - 1)) { + matrixHeight++; + counter = 0; + } else { + counter++; + } + } + this.edits.convolve = { + width: Number(matrixWidth), + height: Number(matrixHeight), + kernel: matrix + } + } + else if (key === ('equalize')) { + this.edits.normalize = "true"; + } + else if (key === ('fill')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + if (!ColorName[value]) { + value = `#${value}` + } + this.edits.resize.background = Color(value).object(); + } + else if (key === ('format')) { + const formattedValue = value.replace(/[^0-9a-z]/gi, '').replace(/jpg/i, 'jpeg'); + const acceptedValues = ['heic', 'heif', 'jpeg', 'png', 'raw', 'tiff', 'webp']; + if (acceptedValues.includes(formattedValue)) { + this.edits.toFormat = formattedValue; + } + } + else if (key === ('grayscale')) { + this.edits.grayscale = true; + } + else if (key === ('no_upscale')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + this.edits.resize.withoutEnlargement = true; + } + else if (key === ('proportion')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + const prop = Number(value); + this.edits.resize.width = Number(this.edits.resize.width * prop); + this.edits.resize.height = Number(this.edits.resize.height * prop); + } + else if (key === ('quality')) { + if (['jpg', 'jpeg'].includes(filetype)) { + this.edits.jpeg = { quality: Number(value) } + } else if (filetype === 'png') { + this.edits.png = { quality: Number(value) } + } else if (filetype === 'webp') { + this.edits.webp = { quality: Number(value) } + } else if (filetype === 'tiff') { + this.edits.tiff = { quality: Number(value) } + } else if (filetype === 'heif') { + this.edits.heif = { quality: Number(value) } + } + } + else if (key === ('rgb')) { + const percentages = value.split(','); + const values = []; + percentages.forEach(function (percentage) { + const parsedPercentage = Number(percentage); + const val = 255 * (parsedPercentage / 100); + values.push(val); + }) + this.edits.tint = { r: values[0], g: values[1], b: values[2] }; + } + else if (key === ('rotate')) { + this.edits.rotate = Number(value); + } + else if (key === ('sharpen')) { + const sh = value.split(','); + const sigma = 1 + Number(sh[1]) / 2; + this.edits.sharpen = sigma; + } + else if (key === ('stretch')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + if (this.sizingMethod === undefined || this.sizingMethod !== 'fit-in') { + this.edits.resize.fit = "fill"; + } + } + else if (key === ('strip_exif')) { + this.edits.rotate = 0; + } + else if (key === ('strip_icc')) { + this.edits.rotate = 0; + } + else if (key === ('upscale')) { + if (this.edits.resize === undefined) { + this.edits.resize = {}; + } + this.edits.resize.fit = "inside" + } + else if (key === ('watermark')) { + const options = value.replace(/\s+/g, '').split(','); + const bucket = options[0]; + const key = options[1]; + const xPos = options[2]; + const yPos = options[3]; + const alpha = options[4]; + const wRatio = options[5]; + const hRatio = options[6]; + + this.edits.overlayWith = { + bucket, + key, + alpha, + wRatio, + hRatio, + options: {} + } + const allowedPosPattern = /^(100|[1-9]?[0-9]|-(100|[1-9][0-9]?))p$/; + if (allowedPosPattern.test(xPos) || !isNaN(xPos)) { + this.edits.overlayWith.options['left'] = xPos; + } + if (allowedPosPattern.test(yPos) || !isNaN(yPos)) { + this.edits.overlayWith.options['top'] = yPos; + } + } + else { + return undefined; + } + } +} + +// Exports +module.exports = ThumborMapping; \ 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 new file mode 100644 index 000000000..76fbb415f --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-konstruk/aws-serverless-image-handler", + "version": "0.8.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" + }, + "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.serverlessimagehandler", + "maven": { + "groupId": "software.amazon.konstruk", + "artifactId": "serverlessimagehandler" + } + }, + "dotnet": { + "namespace": "Amazon.Konstruk.AWS.ServerlessImageHandler", + "packageId": "Amazon.Konstruk.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" + } + } + }, + "dependencies": { + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/core": "~1.25.0", + "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda": "~0.8.0", + "@aws-solutions-konstruk/aws-lambda-s3": "~0.8.0", + "@aws-solutions-konstruk/core": "~0.8.0" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + }, + "peerDependencies": { + "@aws-solutions-konstruk/aws-cloudfront-apigateway-lambda": "~0.8.0", + "@aws-solutions-konstruk/aws-lambda-s3": "~0.8.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "@aws-cdk/aws-iam": "~1.25.0" + } +} diff --git a/source/use_cases/aws-serverless-image-handler/test/__snapshots__/test.serverless-image-handler.test.js.snap b/source/use_cases/aws-serverless-image-handler/test/__snapshots__/test.serverless-image-handler.test.js.snap new file mode 100644 index 000000000..b4d56b094 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/test/__snapshots__/test.serverless-image-handler.test.js.snap @@ -0,0 +1,910 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Minimal deployment snapshot test 1`] = ` +Object { + "Outputs": Object { + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiEndpoint76827D71": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96ArtifactHash7AE27721": Object { + "Description": "Artifact hash for asset \\"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\\"", + "Type": "String", + }, + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3Bucket65CDB50E": Object { + "Description": "S3 bucket for asset \\"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\\"", + "Type": "String", + }, + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1": Object { + "Description": "S3 key for asset version \\"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\\"", + "Type": "String", + }, + }, + "Resources": Object { + "testserverlessimagehandlerCloudFrontApiGatewayLambdaApiAccessLogGroup75A8AB40": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudFrontDistributionCFDistribution5DCC756A": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudfrontLoggingBucket58AA7378", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "CustomOriginConfig": Object { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": Array [ + "TLSv1.2", + ], + }, + "DomainName": Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "/", + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "://", + Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8", + }, + "/", + ], + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + "Id": "origin1", + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudfrontLoggingBucket58AA7378": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiAccount372B2E2D": Object { + "DependsOn": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiCloudWatchRole21DC3987", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiCloudWatchRole21DC3987": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC": Object { + "Properties": Object { + "BinaryMediaTypes": Array [ + "*/*", + ], + "EndpointConfiguration": Object { + "Types": Array [ + "REGIONAL", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYApiPermissionTesttestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi3B1AFDB4ANY1BF514F4": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYApiPermissiontestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi3B1AFDB4ANYA88E54B3": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "/", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYC648EF96": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentD7B20DCAf6e8220e21bde23f88e7afaa751339fe": Object { + "DependsOn": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANY9BF7CFD0", + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4", + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYC648EF96", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaApiAccessLogGroup75A8AB40", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentD7B20DCAf6e8220e21bde23f88e7afaa751339fe", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiUsagePlan6B0FADA4": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "Stage": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANY9BF7CFD0": Object { + "Properties": Object { + "AuthorizationType": "AWS_IAM", + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4", + }, + "RestApiId": Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANYApiPermissionTesttestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi3B1AFDB4ANYproxyC1D1533D": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANYApiPermissiontestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi3B1AFDB4ANYproxy2861CE67": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + }, + "/", + Object { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662": 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": "testserverlessimagehandlerExistingLambdaS3S3LoggingBucket406E2181", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "testserverlessimagehandlerExistingLambdaS3S3LoggingBucket406E2181": 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", + }, + "testserverlessimagehandlerLambdaFunction78B3105C": Object { + "DependsOn": Array [ + "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899", + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3Bucket65CDB50E", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AUTO_WEBP": "No", + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "CORS_ENABLED": "Yes", + "CORS_ORIGIN": "*", + "S3_BUCKET_NAME": Object { + "Ref": "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + }, + "SOURCE_BUCKETS": Object { + "Fn::Join": Array [ + "", + Array [ + "my-sample-bucket,", + Object { + "Ref": "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + }, + ], + ], + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899", + "Roles": Array [ + Object { + "Ref": "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "testserverlessimagehandlerLambdaS3AccessPolicyD6DC56B2": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W12", + "reason": "Specified Rekognition action needs wildcard resource.", + }, + ], + }, + }, + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*", + }, + Object { + "Action": "rekognition:DetectFaces", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "testserverlessimagehandlerLambdaS3AccessPolicyD6DC56B2", + "Roles": Array [ + Object { + "Ref": "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.expected.json b/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.expected.json new file mode 100644 index 000000000..fcdaeb69f --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.expected.json @@ -0,0 +1,907 @@ +{ + "Description": "Integration Test for aws-serverless-image-handler", + "Resources": { + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "BinaryMediaTypes": [ + "*/*" + ], + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Name": "RestApi" + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentD7B20DCAf6e8220e21bde23f88e7afaa751339fe": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANY9BF7CFD0", + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4", + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYC648EF96" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaApiAccessLogGroup75A8AB40", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentD7B20DCAf6e8220e21bde23f88e7afaa751339fe" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANYApiPermissiontestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiAA086636ANYproxy770FE38C": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "/", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANYApiPermissionTesttestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiAA086636ANYproxy5FAC5F91": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxyANY9BF7CFD0": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiproxy5FD5FDA4" + }, + "RestApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYApiPermissiontestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiAA086636ANY706A7345": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "/", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8" + }, + "/*/" + ] + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYApiPermissionTesttestserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiAA086636ANYBCCBE67D": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiANYC648EF96": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "AuthorizationType": "AWS_IAM", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunction78B3105C", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiUsagePlan6B0FADA4": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + "Stage": { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8" + }, + "Throttle": {} + } + ] + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaApiAccessLogGroup75A8AB40": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiCloudWatchRole21DC3987": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiAccount372B2E2D": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaLambdaRestApiCloudWatchRole21DC3987", + "Arn" + ] + } + }, + "DependsOn": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + ] + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudfrontLoggingBucket58AA7378": { + "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 CloudFront Distribution" + }, + { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudFrontDistributionCFDistribution5DCC756A": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "testserverlessimagehandlerCloudFrontApiGatewayLambdaCloudFrontToApiGatewayCloudfrontLoggingBucket58AA7378", + "RegionalDomainName" + ] + }, + "IncludeCookies": false + }, + "Origins": [ + { + "CustomOriginConfig": { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "/", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "://", + { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8" + }, + "/" + ] + ] + } + ] + } + ] + } + ] + } + ] + }, + "Id": "origin1" + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6": { + "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" + } + ] + } + }, + "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899", + "Roles": [ + { + "Ref": "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6" + } + ] + } + }, + "testserverlessimagehandlerLambdaFunction78B3105C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3Bucket65CDB50E" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "AUTO_WEBP": "No", + "CORS_ENABLED": "Yes", + "CORS_ORIGIN": "*", + "S3_BUCKET_NAME": { + "Ref": "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662" + }, + "SOURCE_BUCKETS": { + "Fn::Join": [ + "", + [ + "my-sample-bucket,", + { + "Ref": "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662" + } + ] + ] + } + } + } + }, + "DependsOn": [ + "testserverlessimagehandlerLambdaFunctionServiceRoleDefaultPolicyD1EA3899", + "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6" + ], + "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." + } + ] + } + } + }, + "testserverlessimagehandlerExistingLambdaS3S3LoggingBucket406E2181": { + "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" + } + ] + } + } + }, + "testserverlessimagehandlerExistingLambdaS3S3Bucket9203E662": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "testserverlessimagehandlerExistingLambdaS3S3LoggingBucket406E2181" + } + }, + "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" + } + ] + } + } + }, + "testserverlessimagehandlerLambdaS3AccessPolicyD6DC56B2": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*" + }, + { + "Action": "rekognition:DetectFaces", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testserverlessimagehandlerLambdaS3AccessPolicyD6DC56B2", + "Roles": [ + { + "Ref": "testserverlessimagehandlerLambdaFunctionServiceRole744A1CF6" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W12", + "reason": "Specified Rekognition action needs wildcard resource." + } + ] + } + } + } + }, + "Outputs": { + "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiEndpoint76827D71": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApi2376E6FC" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "testserverlessimagehandlerCloudFrontApiGatewayLambdaRestApiDeploymentStageprodBE3E04B8" + }, + "/" + ] + ] + } + } + }, + "Parameters": { + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3Bucket65CDB50E": { + "Type": "String", + "Description": "S3 bucket for asset \"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\"" + }, + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96S3VersionKeyCF89D2F1": { + "Type": "String", + "Description": "S3 key for asset version \"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\"" + }, + "AssetParameters5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96ArtifactHash7AE27721": { + "Type": "String", + "Description": "Artifact hash for asset \"5f752add658f79e0005d882c0bc5a08dc38a14dc55135d974ea1d2226cb28b96\"" + } + } +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.ts b/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.ts new file mode 100644 index 000000000..fecc86751 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/test/integ.basic-deployment.ts @@ -0,0 +1,34 @@ +/** + * 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 { App, Stack } from "@aws-cdk/core"; +import { ServerlessImageHandler, ServerlessImageHandlerProps } from "../lib"; + +// Setup +const app = new App(); +const stack = new Stack(app, 'test-serverless-image-handler'); +stack.templateOptions.description = 'Integration Test for aws-serverless-image-handler'; + +// Definitions +const props: ServerlessImageHandlerProps = { + corsEnabled: true, + corsOrigin: "*", + sourceBuckets: "my-sample-bucket", + logRetentionPeriod: 7 +}; + +new ServerlessImageHandler(stack, 'test-serverless-image-handler', props); + +// Synth +app.synth(); diff --git a/source/use_cases/aws-serverless-image-handler/test/test.serverless-image-handler.test.ts b/source/use_cases/aws-serverless-image-handler/test/test.serverless-image-handler.test.ts new file mode 100644 index 000000000..d8b1b2a13 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/test/test.serverless-image-handler.test.ts @@ -0,0 +1,74 @@ +/** + * 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 { ServerlessImageHandler, ServerlessImageHandlerProps } from "../lib"; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +// -------------------------------------------------------------- +// Minimal deployment snapshot test +// -------------------------------------------------------------- +test('Minimal deployment snapshot test', () => { + // Initial Setup + const stack = new Stack(); + const props: ServerlessImageHandlerProps = { + corsEnabled: true, + corsOrigin: "*", + sourceBuckets: "my-sample-bucket", + logRetentionPeriod: 7 + }; + new ServerlessImageHandler(stack, 'test-serverless-image-handler', props); + // Assertion 1 + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +// -------------------------------------------------------------- +// Custom deployment unit testing +// -------------------------------------------------------------- +test('Custom deployment unit testing', () => { + // Initial Setup + const stack = new Stack(); + const props: ServerlessImageHandlerProps = { + corsEnabled: false, + sourceBuckets: "", + logRetentionPeriod: 7, + autoWebP: true, + customProps: { + lambdaFunctionProps: { + environment: { + TEST_KEY: "TEST_VALUE" + } + }, + cloudFrontDistributionProps: { + enableIpV6: true + }, + apiGatewayProps: { + failOnWarnings: true + }, + bucketPermissions: ['ReadWrite'] + } + }; + const sih = new ServerlessImageHandler(stack, 'test-serverless-image-handler', props); + // Assertion 1 + expect(sih.lambdaFunction()).toBeDefined(); + // Assertion 2 + expect(sih.s3Bucket()).toBeDefined(); + // Assertion 3 + expect(sih.apiGateway()).toBeDefined(); + // Assertion 4 + expect(sih.cloudFrontDistribution()).toBeDefined(); + // Assertion 5 + expect(sih.lambdaFunction()).toHaveProperty('environment.TEST_KEY', 'TEST_VALUE'); +}); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-image-handler/tsconfig.json b/source/use_cases/aws-serverless-image-handler/tsconfig.json new file mode 100644 index 000000000..07afb1f64 --- /dev/null +++ b/source/use_cases/aws-serverless-image-handler/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2018" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} diff --git a/source/use_cases/aws-serverless-web-app/.eslintignore b/source/use_cases/aws-serverless-web-app/.eslintignore new file mode 100644 index 000000000..f48b2d714 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/.eslintignore @@ -0,0 +1,7 @@ +lib/**/*.js +test/*.js +bin/*.js +*.d.ts +coverage +test/lambda/index.js +cdk.out/**/*.js \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/.gitignore b/source/use_cases/aws-serverless-web-app/.gitignore new file mode 100644 index 000000000..96e33d0f7 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/.gitignore @@ -0,0 +1,16 @@ +lib/*.js +test/*.js +bin/*.js +*.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/use_cases/aws-serverless-web-app/.npmignore b/source/use_cases/aws-serverless-web-app/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/.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/use_cases/aws-serverless-web-app/README.md b/source/use_cases/aws-serverless-web-app/README.md new file mode 100644 index 000000000..f075cfca8 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/README.md @@ -0,0 +1,36 @@ +# AWS Serverless Web App Use Case + +This use case 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. + +## Architecture +The application architecture uses AWS Lambda, Amazon API Gateway, Amazon S3, Amazon DynamoDB, and Amazon Cognito as pictured below: +![Architecture Diagram](architecture.png) + +## Deployment steps +Below are the steps to deploy the use case: + +``` +npm run build + +cdk deploy + +``` + +## Deployment Verification +After the stack is deployed successfully, go to the Outputs tab in AWS Cloudformation console of S3StaticWebsiteStack, it should show the 'websiteURL', click on the link and follow the steps below: + +* Visit /register.html under your website domain, register youself. + +* Verify the registered user email. + +* Visit /ride.html under your website domain. + +* If you are redirected to the sign in page, sign in with the user you created in the previous module. + +* After the map has loaded, click anywhere on the map to set a pickup location. + +* Choose Request Unicorn. You should see a notification in the right sidebar that a unicorn is on its way and then see a unicorn icon fly to your pickup location. + + +*** +© Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/architecture.png b/source/use_cases/aws-serverless-web-app/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..319b48ab014b4c7422a52b789d90cd76cd9afc84 GIT binary patch literal 22981 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 literal 0 HcmV?d00001 diff --git a/source/use_cases/aws-serverless-web-app/bin/serverless-web-app.ts b/source/use_cases/aws-serverless-web-app/bin/serverless-web-app.ts new file mode 100644 index 000000000..4b2b4f15a --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/bin/serverless-web-app.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +/** + * 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 'source-map-support/register'; +import * as cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; +import { ServerlessBackendStack } from '../lib/serverless-backend-stack'; + +const app = new cdk.App(); +const stack1 = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); +const stack2 = new ServerlessBackendStack(app, 'ServerlessBackendStack'); +stack2.addDependency(stack1); +stack1.templateOptions.description = 'Creates a static website using AWS S3 and Amazon Cloudfront'; +stack2.templateOptions.description = 'Creates a serverless backend using Amazon Cognito, Amazon API Gateway, AWS Lambda function and Amazon DynamoDB table'; \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/cdk.json b/source/use_cases/aws-serverless-web-app/cdk.json new file mode 100644 index 000000000..3a62a7677 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/serverless-web-app.ts" +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/lib/lambda/business-logic/index.js b/source/use_cases/aws-serverless-web-app/lib/lambda/business-logic/index.js new file mode 100644 index 000000000..2532f128a --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/lib/lambda/business-logic/index.js @@ -0,0 +1,119 @@ +const randomBytes = require('crypto').randomBytes; + +const AWS = require('aws-sdk'); + +const ddb = new AWS.DynamoDB.DocumentClient(); + +const fleet = [ + { + Name: 'Bucephalus', + Color: 'Golden', + Gender: 'Male', + }, + { + Name: 'Shadowfax', + Color: 'White', + Gender: 'Male', + }, + { + Name: 'Rocinante', + Color: 'Yellow', + Gender: 'Female', + }, +]; + +exports.handler = (event, context, callback) => { + if (!event.requestContext.authorizer) { + errorResponse('Authorization not configured', context.awsRequestId, callback); + return; + } + + const rideId = toUrlString(randomBytes(16)); + console.log('Received event (', rideId, '): ', event); + + // Because we're using a Cognito User Pools authorizer, all of the claims + // included in the authentication token are provided in the request context. + // This includes the username as well as other attributes. + const username = event.requestContext.authorizer.claims['cognito:username']; + + // The body field of the event in a proxy integration is a raw string. + // In order to extract meaningful values, we need to first parse this string + // into an object. A more robust implementation might inspect the Content-Type + // header first and use a different parsing strategy based on that value. + const requestBody = JSON.parse(event.body); + + const pickupLocation = requestBody.PickupLocation; + + const unicorn = findUnicorn(pickupLocation); + + recordRide(rideId, username, unicorn).then(() => { + // You can use the callback function to provide a return value from your Node.js + // Lambda functions. The first parameter is used for failed invocations. The + // second parameter specifies the result data of the invocation. + + // Because this Lambda function is called by an API Gateway proxy integration + // the result object must use the following structure. + callback(null, { + statusCode: 201, + body: JSON.stringify({ + RideId: rideId, + Unicorn: unicorn, + UnicornName: unicorn.Name, + Eta: '30 seconds', + Rider: username, + }), + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }); + }).catch((err) => { + console.error(err); + + // If there is an error during processing, catch it and return + // from the Lambda function successfully. Specify a 500 HTTP status + // code and provide an error message in the body. This will provide a + // more meaningful error response to the end client. + errorResponse(err.message, context.awsRequestId, callback) + }); +}; + +// This is where you would implement logic to find the optimal unicorn for +// this ride (possibly invoking another Lambda function as a microservice.) +// For simplicity, we'll just pick a unicorn at random. +function findUnicorn(pickupLocation) { + console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude); + return fleet[Math.floor(Math.random() * fleet.length)]; +} + +function recordRide(rideId, username, unicorn) { + return ddb.put({ + TableName: 'Rides', + Item: { + RideId: rideId, + User: username, + Unicorn: unicorn, + UnicornName: unicorn.Name, + RequestTime: new Date().toISOString(), + }, + }).promise(); +} + +function toUrlString(buffer) { + return buffer.toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} + +function errorResponse(errorMessage, awsRequestId, callback) { + callback(null, { + statusCode: 500, + body: JSON.stringify({ + Error: errorMessage, + Reference: awsRequestId, + }), + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }); +} diff --git a/source/use_cases/aws-serverless-web-app/lib/lambda/cognito-config/update_s3_object.py b/source/use_cases/aws-serverless-web-app/lib/lambda/cognito-config/update_s3_object.py new file mode 100644 index 000000000..63b6376ed --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/lib/lambda/cognito-config/update_s3_object.py @@ -0,0 +1,46 @@ +import json +import boto3 +import logging +from botocore.exceptions import ClientError + +logger = logging.getLogger() +logger.setLevel(logging.INFO) +s3 = boto3.resource('s3') + +def on_event(event, context): + logger.info("Received event: %s" % json.dumps(event)) + request_type = event['RequestType'] + if request_type == 'Create': return on_create(event) + if request_type == 'Update': return on_create(event) + if request_type == 'Delete': return + raise Exception("Invalid request type: %s" % request_type) + +def on_create(event): + properties = event['ResourceProperties'] + userPoolId = properties['UserPool'] + clientId = properties['Client'] + region = properties['Region'] + bucket = properties['Bucket'] + restapi = properties['RestApi'] + + try: + s3.Object(bucket, 'js/config.js') + config_content = """ + var _config = { + cognito: { + userPoolId: '%s', // e.g. us-east-2_uXboG5pAb + userPoolClientId: '%s', // e.g. 25ddkmj4v6hfsfvruhpfi7n4hv + region: '%s', // e.g. us-east-2 + }, + api: { + invokeUrl: '%s', // e.g. https://rc7nyt4tql.execute-api.us-west-2.amazonaws.com/prod' + } + }; + """ + config_content = config_content % (userPoolId, clientId, region, restapi) + config = s3.Object(bucket,'js/config.js') + config.put(Body=config_content) + except ClientError as e: + logger.error('Error: %s', e) + raise e + return \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/lib/lambda/static-content/copy_s3_objects.py b/source/use_cases/aws-serverless-web-app/lib/lambda/static-content/copy_s3_objects.py new file mode 100644 index 000000000..b31888dbd --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/lib/lambda/static-content/copy_s3_objects.py @@ -0,0 +1,56 @@ +import os +import json +import boto3 +from botocore.exceptions import ClientError +client = boto3.client('s3') + +import logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +def on_event(event, context): + logger.info("Received event: %s" % json.dumps(event)) + request_type = event['RequestType'] + if request_type == 'Create': return on_create(event) + if request_type == 'Update': return on_create(event) + if request_type == 'Delete': return on_delete(event) + raise Exception("Invalid request type: %s" % request_type) + +def on_create(event): + source_bucket = event['ResourceProperties']['SourceBucket'] + source_prefix = event['ResourceProperties'].get('SourcePrefix') or '' + bucket = event['ResourceProperties']['Bucket'] + prefix = event['ResourceProperties'].get('Prefix') or '' + try: + copy_objects(source_bucket, source_prefix, bucket, prefix) + except ClientError as e: + logger.error('Error: %s', e) + raise e + return + +def on_delete(event): + bucket = event['ResourceProperties']['Bucket'] + prefix = event['ResourceProperties'].get('Prefix') or '' + try: + delete_objects(bucket, prefix) + except ClientError as e: + logger.error('Error: %s', e) + raise e + return + +def copy_objects(source_bucket, source_prefix, bucket, prefix): + paginator = client.get_paginator('list_objects_v2') + page_iterator = paginator.paginate(Bucket=source_bucket, Prefix=source_prefix) + for key in {x['Key'] for page in page_iterator for x in page['Contents']}: + dest_key = os.path.join(prefix, os.path.relpath(key, source_prefix)) + if not key.endswith('/'): + #logger.info("copy %s to %s".format(key, dest_key)) + client.copy_object(CopySource={'Bucket': source_bucket, 'Key': key}, Bucket=bucket, Key = dest_key) + return + +def delete_objects(bucket, prefix): + paginator = client.get_paginator('list_objects_v2') + page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix) + objects = [{'Key': x['Key']} for page in page_iterator for x in page['Contents']] + client.delete_objects(Bucket=bucket, Delete={'Objects': objects}) + return \ No newline at end of file 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 new file mode 100644 index 000000000..646f28893 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/lib/s3-static-site-stack.ts @@ -0,0 +1,82 @@ +/** + * 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 { Construct, Stack, StackProps, Duration, CfnOutput } from '@aws-cdk/core'; +import { CloudFrontToS3 } from '@aws-solutions-konstruk/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'; +import { PolicyStatement } from '@aws-cdk/aws-iam'; + +export class S3StaticWebsiteStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const sourceBucket: string = 'wildrydes-us-east-1'; + const sourcePrefix: string = 'WebApplication/1_StaticWebHosting/website/'; + + const konstruk = new CloudFrontToS3(this, 'CloudFrontToS3', { + deployBucket: true + }); + const targetBucket: string = konstruk.bucket().bucketName; + + const lambdaFunc = new lambda.Function(this, 'staticContentHandler', { + runtime: lambda.Runtime.PYTHON_3_8, + handler: 'copy_s3_objects.on_event', + code: lambda.Code.fromAsset(`${__dirname}/lambda/static-content`), + timeout: Duration.minutes(5), + initialPolicy: [ + new PolicyStatement({ + actions: ["s3:GetObject", + "s3:ListBucket"], + resources: [`arn:aws:s3:::${sourceBucket}`, + `arn:aws:s3:::${sourceBucket}/${sourcePrefix}*`] + }), + new PolicyStatement({ + actions: ["s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject"], + resources: [`arn:aws:s3:::${targetBucket}`, + `arn:aws:s3:::${targetBucket}/*`] + }), + ] + }); + + const customResourceProvider = new Provider(this, 'CustomResourceProvider', { + onEventHandler: lambdaFunc + }); + + new CustomResource(this, 'CustomResource', { + provider: customResourceProvider, + properties: { + SourceBucket: sourceBucket, + SourcePrefix: sourcePrefix, + Bucket: targetBucket + } + }); + + new CfnOutput(this, 'websiteURL', { + value: 'https://' + konstruk.cloudFrontWebDistribution().domainName + }); + + new CfnOutput(this, 'websiteBucket', { + value: targetBucket, + exportName: 'websiteBucket' + }); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..20937d0e6 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/lib/serverless-backend-stack.ts @@ -0,0 +1,92 @@ +/** + * 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 { CognitoToApiGatewayToLambda } from '@aws-solutions-konstruk/aws-cognito-apigateway-lambda'; +import { LambdaToDynamoDB } from '@aws-solutions-konstruk/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'; +import { CustomResource } from '@aws-cdk/aws-cloudformation'; +import { PolicyStatement } from '@aws-cdk/aws-iam'; +import { UserPoolAttribute } from '@aws-cdk/aws-cognito'; +import { Cors } from '@aws-cdk/aws-apigateway'; +import { AttributeType } from '@aws-cdk/aws-dynamodb'; + +export class ServerlessBackendStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const websiteBucketName: string = Fn.importValue('websiteBucket'); + + const konstruk = new CognitoToApiGatewayToLambda(this, 'CognitoToApiGatewayToLambda', { + deployLambda: true, + lambdaFunctionProps: { + code: lambda.Code.asset(`${__dirname}/lambda/business-logic`), + runtime: lambda.Runtime.NODEJS_12_X, + handler: 'index.handler' + }, + cognitoUserPoolProps: { + userPoolName: 'WileRydes', + autoVerifiedAttributes: [UserPoolAttribute.EMAIL] + }, + apiGatewayProps: { + defaultCorsPreflightOptions: { + allowOrigins: Cors.ALL_ORIGINS, + allowMethods: Cors.ALL_METHODS + } + } + }); + + const lambdaFunc = new lambda.Function(this, 'updateConfigHandler', { + runtime: lambda.Runtime.PYTHON_3_8, + handler: 'update_s3_object.on_event', + code: lambda.Code.fromAsset(`${__dirname}/lambda/cognito-config`), + timeout: Duration.minutes(5), + initialPolicy: [ + new PolicyStatement({ + actions: ["s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl"], + resources: [`arn:aws:s3:::${websiteBucketName}/*`] + }), + ] + }); + + const customResourceProvider = new Provider(this, 'CustomResourceProvider', { + onEventHandler: lambdaFunc + }); + + new CustomResource(this, 'CustomResource', { + provider: customResourceProvider, + properties: { + UserPool: konstruk.userPool().userPoolId, + Client: konstruk.userPoolClient().userPoolClientId, + Region: Stack.of(this).region, + Bucket: websiteBucketName, + RestApi: konstruk.restApi().url + } + }); + + new LambdaToDynamoDB(this, 'LambdaToDynamoDB', { + deployLambda: false, + existingLambdaObj: konstruk.lambdaFunction(), + dynamoTableProps: { + tableName: 'Rides', + partitionKey: { + name: 'RideId', + type: AttributeType.STRING + } + } + }); + } +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/package.json b/source/use_cases/aws-serverless-web-app/package.json new file mode 100644 index 000000000..c2b42fe3c --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/package.json @@ -0,0 +1,57 @@ +{ + "name": "@aws-solutions-konstruk/aws-serverless-web-app", + "version": "0.8.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", + "directory": "source/use_cases/aws-serverless-web-app" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc", + "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", + "build+lint+test": "npm run build && npm run lint && npm test && npm run integ-assert" + }, + "dependencies": { + "@aws-solutions-konstruk/aws-cloudfront-s3": "~0.8.0", + "@aws-solutions-konstruk/aws-cognito-apigateway-lambda": "~0.8.0", + "@aws-solutions-konstruk/aws-lambda-dynamodb": "~0.8.0", + "@aws-cdk/core": "~1.25.0", + "@aws-cdk/aws-lambda": "~1.25.0", + "@aws-cdk/aws-cloudfront": "~1.25.0", + "@aws-cdk/aws-s3": "~1.25.0", + "@aws-cdk/custom-resources": "~1.25.0", + "@aws-cdk/aws-cloudformation": "~1.25.0", + "@aws-cdk/aws-iam": "~1.25.0", + "@aws-cdk/aws-cognito": "~1.25.0", + "@aws-cdk/aws-apigateway": "~1.25.0", + "@aws-cdk/aws-dynamodb": "~1.25.0", + "@aws-solutions-konstruk/core": "~0.8.0", + "source-map-support": "^0.5.16" + }, + "devDependencies": { + "@aws-cdk/assert": "~1.25.0", + "@types/jest": "^24.0.23", + "@types/node": "^10.3.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ] + } +} diff --git a/source/use_cases/aws-serverless-web-app/test/__snapshots__/s3-static-site-stack.test.js.snap b/source/use_cases/aws-serverless-web-app/test/__snapshots__/s3-static-site-stack.test.js.snap new file mode 100644 index 000000000..76b778abc --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/__snapshots__/s3-static-site-stack.test.js.snap @@ -0,0 +1,634 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`default stack 1`] = ` +Object { + "Outputs": Object { + "websiteBucket": Object { + "Export": Object { + "Name": "websiteBucket", + }, + "Value": Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + }, + "websiteURL": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E", + "DomainName", + ], + }, + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36ArtifactHash171A5ECB": Object { + "Description": "Artifact hash for asset \\"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\\"", + "Type": "String", + }, + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3BucketE560DEC2": Object { + "Description": "S3 bucket for asset \\"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\\"", + "Type": "String", + }, + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665": Object { + "Description": "S3 key for asset version \\"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": Object { + "Description": "Artifact hash for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": Object { + "Description": "S3 bucket for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": Object { + "Description": "S3 key for asset version \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + }, + "Resources": Object { + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E": Object { + "Properties": Object { + "DistributionConfig": Object { + "DefaultCacheBehavior": Object { + "AllowedMethods": Array [ + "GET", + "HEAD", + ], + "CachedMethods": Array [ + "GET", + "HEAD", + ], + "Compress": true, + "ForwardedValues": Object { + "Cookies": Object { + "Forward": "none", + }, + "QueryString": false, + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": Object { + "Bucket": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B", + "RegionalDomainName", + ], + }, + "IncludeCookies": false, + }, + "Origins": Array [ + Object { + "DomainName": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "RegionalDomainName", + ], + }, + "Id": "origin1", + "S3OriginConfig": Object { + "OriginAccessIdentity": Object { + "Fn::Join": Array [ + "", + Array [ + "origin-access-identity/cloudfront/", + Object { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + }, + ], + ], + }, + }, + }, + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": Object { + "CloudFrontDefaultCertificate": true, + }, + }, + }, + "Type": "AWS::CloudFront::Distribution", + }, + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91": Object { + "Properties": Object { + "CloudFrontOriginAccessIdentityConfig": Object { + "Comment": "Access S3 bucket content only through CloudFront", + }, + }, + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + }, + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B": 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 CloudFront Distribution", + }, + Object { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution", + }, + ], + }, + }, + "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", + }, + "CloudFrontToS3S3Bucket9CE6AB04": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "BucketEncryption": Object { + "ServerSideEncryptionConfiguration": Array [ + Object { + "ServerSideEncryptionByDefault": Object { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "LoggingConfiguration": Object { + "DestinationBucketName": Object { + "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2", + }, + }, + "PublicAccessBlockConfiguration": Object { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "VersioningConfiguration": Object { + "Status": "Enabled", + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "CloudFrontToS3S3BucketPolicy2495300D": Object { + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "F16", + "reason": "Public website bucket policy requires a wildcard principal", + }, + ], + }, + }, + "Properties": Object { + "Bucket": Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + ], + "Effect": "Allow", + "Principal": Object { + "AWS": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + Object { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + }, + ], + ], + }, + }, + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + Object { + "Action": "s3:GetObject", + "Effect": "Allow", + "Principal": Object { + "CanonicalUser": Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + "S3CanonicalUserId", + ], + }, + }, + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::GetAtt": Array [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "CloudFrontToS3S3LoggingBucketEF5CD8B2": 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", + }, + "CustomResource": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "Bucket": Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn", + ], + }, + "SourceBucket": "wildrydes-us-east-1", + "SourcePrefix": "WebApplication/1_StaticWebHosting/website/", + }, + "Type": "AWS::CloudFormation::CustomResource", + "UpdateReplacePolicy": "Delete", + }, + "CustomResourceProviderframeworkonEvent0AA4376C": Object { + "DependsOn": Array [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "USER_ON_EVENT_FUNCTION_ARN": Object { + "Fn::GetAtt": Array [ + "staticContentHandlerC21DFC88", + "Arn", + ], + }, + }, + }, + "Handler": "framework.onEvent", + "Role": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "staticContentHandlerC21DFC88", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": Array [ + Object { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "staticContentHandlerC21DFC88": Object { + "DependsOn": Array [ + "staticContentHandlerServiceRoleDefaultPolicy0F5C5865", + "staticContentHandlerServiceRole3B648F21", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3BucketE560DEC2", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "copy_s3_objects.on_event", + "Role": Object { + "Fn::GetAtt": Array [ + "staticContentHandlerServiceRole3B648F21", + "Arn", + ], + }, + "Runtime": "python3.8", + "Timeout": 300, + }, + "Type": "AWS::Lambda::Function", + }, + "staticContentHandlerServiceRole3B648F21": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "staticContentHandlerServiceRoleDefaultPolicy0F5C5865": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:GetObject", + "s3:ListBucket", + ], + "Effect": "Allow", + "Resource": Array [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*", + ], + }, + Object { + "Action": Array [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:s3:::", + Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + ], + ], + }, + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:s3:::", + Object { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04", + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "staticContentHandlerServiceRoleDefaultPolicy0F5C5865", + "Roles": Array [ + Object { + "Ref": "staticContentHandlerServiceRole3B648F21", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/use_cases/aws-serverless-web-app/test/__snapshots__/serverless-backend-stack.test.js.snap b/source/use_cases/aws-serverless-web-app/test/__snapshots__/serverless-backend-stack.test.js.snap new file mode 100644 index 000000000..331872706 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/__snapshots__/serverless-backend-stack.test.js.snap @@ -0,0 +1,1096 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`default stack 1`] = ` +Object { + "Outputs": Object { + "RestApiEndpoint0551178A": Object { + "Value": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + }, + }, + "Parameters": Object { + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791ArtifactHashF30FBC00": Object { + "Description": "Artifact hash for asset \\"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\\"", + "Type": "String", + }, + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3Bucket928903EC": Object { + "Description": "S3 bucket for asset \\"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\\"", + "Type": "String", + }, + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD": Object { + "Description": "S3 key for asset version \\"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\\"", + "Type": "String", + }, + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360ArtifactHash5ED5576F": Object { + "Description": "Artifact hash for asset \\"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\\"", + "Type": "String", + }, + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3Bucket20EEB389": Object { + "Description": "S3 bucket for asset \\"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\\"", + "Type": "String", + }, + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577": Object { + "Description": "S3 key for asset version \\"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": Object { + "Description": "Artifact hash for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": Object { + "Description": "S3 bucket for asset \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": Object { + "Description": "S3 key for asset version \\"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\\"", + "Type": "String", + }, + }, + "Resources": Object { + "ApiAccessLogGroupCEA70788": Object { + "DeletionPolicy": "Retain", + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "CognitoAuthorizer": Object { + "Properties": Object { + "IdentitySource": "method.request.header.Authorization", + "Name": "authorizer", + "ProviderARNs": Array [ + Object { + "Fn::GetAtt": Array [ + "CognitoUserPool53E37E69", + "Arn", + ], + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Type": "COGNITO_USER_POOLS", + }, + "Type": "AWS::ApiGateway::Authorizer", + }, + "CognitoUserPool53E37E69": Object { + "Properties": Object { + "AutoVerifiedAttributes": Array [ + "email", + ], + "LambdaConfig": Object {}, + "UserPoolAddOns": Object { + "AdvancedSecurityMode": "ENFORCED", + }, + "UserPoolName": "WileRydes", + }, + "Type": "AWS::Cognito::UserPool", + }, + "CognitoUserPoolClient5AB59AE4": Object { + "Properties": Object { + "UserPoolId": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::Cognito::UserPoolClient", + }, + "CustomResource": Object { + "DeletionPolicy": "Delete", + "Properties": Object { + "Bucket": Object { + "Fn::ImportValue": "websiteBucket", + }, + "Client": Object { + "Ref": "CognitoUserPoolClient5AB59AE4", + }, + "Region": Object { + "Ref": "AWS::Region", + }, + "RestApi": Object { + "Fn::Join": Array [ + "", + Array [ + "https://", + Object { + "Ref": "RestApi0C43BF4B", + }, + ".execute-api.", + Object { + "Ref": "AWS::Region", + }, + ".", + Object { + "Ref": "AWS::URLSuffix", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/", + ], + ], + }, + "ServiceToken": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn", + ], + }, + "UserPool": Object { + "Ref": "CognitoUserPool53E37E69", + }, + }, + "Type": "AWS::CloudFormation::CustomResource", + "UpdateReplacePolicy": "Delete", + }, + "CustomResourceProviderframeworkonEvent0AA4376C": Object { + "DependsOn": Array [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "USER_ON_EVENT_FUNCTION_ARN": Object { + "Fn::GetAtt": Array [ + "updateConfigHandler59840941", + "Arn", + ], + }, + }, + }, + "Handler": "framework.onEvent", + "Role": Object { + "Fn::GetAtt": Array [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn", + ], + }, + "Runtime": "nodejs10.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": Object { + "Fn::GetAtt": Array [ + "updateConfigHandler59840941", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": Array [ + Object { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "LambdaFunctionBF21E41F": Object { + "DependsOn": Array [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "LambdaFunctionServiceRole0C4CDE0B", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "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.", + }, + ], + }, + }, + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3Bucket20EEB389", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Environment": Object { + "Variables": Object { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": Object { + "Ref": "LambdaToDynamoDBDynamoTable53C1442D", + }, + }, + }, + "Handler": "index.handler", + "Role": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn", + ], + }, + "Runtime": "nodejs12.x", + }, + "Type": "AWS::Lambda::Function", + }, + "LambdaFunctionServiceRole0C4CDE0B": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":log-group:/aws/lambda/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + ], + "Effect": "Allow", + "Resource": Array [ + Object { + "Fn::GetAtt": Array [ + "LambdaToDynamoDBDynamoTable53C1442D", + "Arn", + ], + }, + Object { + "Ref": "AWS::NoValue", + }, + ], + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": Array [ + Object { + "Ref": "LambdaFunctionServiceRole0C4CDE0B", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "LambdaRestApiAccount": Object { + "DependsOn": Array [ + "RestApi0C43BF4B", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "LambdaRestApiCloudWatchRoleF339D4E6": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "apigateway.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Policies": Array [ + Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:logs:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "LambdaToDynamoDBDynamoTable53C1442D": Object { + "DeletionPolicy": "Retain", + "Properties": Object { + "AttributeDefinitions": Array [ + Object { + "AttributeName": "RideId", + "AttributeType": "S", + }, + ], + "BillingMode": "PAY_PER_REQUEST", + "KeySchema": Array [ + Object { + "AttributeName": "RideId", + "KeyType": "HASH", + }, + ], + "SSESpecification": Object { + "SSEEnabled": true, + }, + "TableName": "Rides", + }, + "Type": "AWS::DynamoDB::Table", + "UpdateReplacePolicy": "Retain", + }, + "RestApi0C43BF4B": Object { + "Properties": Object { + "EndpointConfiguration": Object { + "Types": Array [ + "EDGE", + ], + }, + "Name": "RestApi", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "RestApiANYA7C1DC94": Object { + "Properties": Object { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": Object { + "Ref": "CognitoAuthorizer", + }, + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiANYApiPermissionServerlessBackendStackRestApi00D08F58ANYEEE63A3B": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiANYApiPermissionTestServerlessBackendStackRestApi00D08F58ANY9F8B1D52": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiDeployment180EC5030503bead3cacf3da667720719058e886": Object { + "DependsOn": Array [ + "RestApiproxyANY1786B242", + "RestApiproxyOPTIONS32C4B154", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + "RestApiOPTIONS6AA64D2D", + ], + "Metadata": Object { + "cfn_nag": Object { + "rules_to_suppress": Array [ + Object { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource", + }, + ], + }, + }, + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "RestApiDeploymentStageprod3855DE66": Object { + "Properties": Object { + "AccessLogSetting": Object { + "DestinationArn": Object { + "Fn::GetAtt": Array [ + "ApiAccessLogGroupCEA70788", + "Arn", + ], + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \\"$context.httpMethod $context.resourcePath $context.protocol\\" $context.status $context.responseLength $context.requestId", + }, + "DeploymentId": Object { + "Ref": "RestApiDeployment180EC5030503bead3cacf3da667720719058e886", + }, + "MethodSettings": Array [ + Object { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*", + }, + ], + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "RestApiOPTIONS6AA64D2D": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": Object { + "IntegrationResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": Object { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiUsagePlan6E1C537A": Object { + "Properties": Object { + "ApiStages": Array [ + Object { + "ApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + "Stage": Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "Throttle": Object {}, + }, + ], + }, + "Type": "AWS::ApiGateway::UsagePlan", + }, + "RestApiproxyANY1786B242": Object { + "Properties": Object { + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": Object { + "Ref": "CognitoAuthorizer", + }, + "HttpMethod": "ANY", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "RestApiproxyANYApiPermissionServerlessBackendStackRestApi00D08F58ANYproxy20E632AA": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/", + Object { + "Ref": "RestApiDeploymentStageprod3855DE66", + }, + "/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyANYApiPermissionTestServerlessBackendStackRestApi00D08F58ANYproxyF3BB47AD": Object { + "Properties": Object { + "Action": "lambda:InvokeFunction", + "FunctionName": Object { + "Fn::GetAtt": Array [ + "LambdaFunctionBF21E41F", + "Arn", + ], + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":execute-api:", + Object { + "Ref": "AWS::Region", + }, + ":", + Object { + "Ref": "AWS::AccountId", + }, + ":", + Object { + "Ref": "RestApi0C43BF4B", + }, + "/test-invoke-stage/*/{proxy+}", + ], + ], + }, + }, + "Type": "AWS::Lambda::Permission", + }, + "RestApiproxyC95856DD": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "RestApi0C43BF4B", + "RootResourceId", + ], + }, + "PathPart": "{proxy+}", + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "RestApiproxyOPTIONS32C4B154": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "OPTIONS", + "Integration": Object { + "IntegrationResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + }, + "StatusCode": "204", + }, + ], + "RequestTemplates": Object { + "application/json": "{ statusCode: 200 }", + }, + "Type": "MOCK", + }, + "MethodResponses": Array [ + Object { + "ResponseParameters": Object { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Methods": true, + "method.response.header.Access-Control-Allow-Origin": true, + }, + "StatusCode": "204", + }, + ], + "ResourceId": Object { + "Ref": "RestApiproxyC95856DD", + }, + "RestApiId": Object { + "Ref": "RestApi0C43BF4B", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "updateConfigHandler59840941": Object { + "DependsOn": Array [ + "updateConfigHandlerServiceRoleDefaultPolicy157F28C3", + "updateConfigHandlerServiceRole3B176B96", + ], + "Properties": Object { + "Code": Object { + "S3Bucket": Object { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3Bucket928903EC", + }, + "S3Key": Object { + "Fn::Join": Array [ + "", + Array [ + Object { + "Fn::Select": Array [ + 0, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD", + }, + ], + }, + ], + }, + Object { + "Fn::Select": Array [ + 1, + Object { + "Fn::Split": Array [ + "||", + Object { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD", + }, + ], + }, + ], + }, + ], + ], + }, + }, + "Handler": "update_s3_object.on_event", + "Role": Object { + "Fn::GetAtt": Array [ + "updateConfigHandlerServiceRole3B176B96", + "Arn", + ], + }, + "Runtime": "python3.8", + "Timeout": 300, + }, + "Type": "AWS::Lambda::Function", + }, + "updateConfigHandlerServiceRole3B176B96": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "updateConfigHandlerServiceRoleDefaultPolicy157F28C3": Object { + "Properties": Object { + "PolicyDocument": Object { + "Statement": Array [ + Object { + "Action": Array [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + ], + "Effect": "Allow", + "Resource": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:aws:s3:::", + Object { + "Fn::ImportValue": "websiteBucket", + }, + "/*", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "updateConfigHandlerServiceRoleDefaultPolicy157F28C3", + "Roles": Array [ + Object { + "Ref": "updateConfigHandlerServiceRole3B176B96", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + }, +} +`; diff --git a/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.expected.json b/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.expected.json new file mode 100644 index 000000000..b626e9eb7 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.expected.json @@ -0,0 +1,1092 @@ +{ + "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" + } + ] + } + }, + "LambdaFunctionServiceRoleDefaultPolicy126C8897": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:Query", + "dynamodb:GetItem", + "dynamodb:Scan", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "LambdaToDynamoDBDynamoTable53C1442D", + "Arn" + ] + }, + { + "Ref": "AWS::NoValue" + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "Roles": [ + { + "Ref": "LambdaFunctionServiceRole0C4CDE0B" + } + ] + } + }, + "LambdaFunctionBF21E41F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3Bucket20EEB389" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionServiceRole0C4CDE0B", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Environment": { + "Variables": { + "AWS_NODEJS_CONNECTION_REUSE_ENABLED": "1", + "DDB_TABLE_NAME": { + "Ref": "LambdaToDynamoDBDynamoTable53C1442D" + } + } + } + }, + "DependsOn": [ + "LambdaFunctionServiceRoleDefaultPolicy126C8897", + "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." + } + ] + } + } + }, + "RestApi0C43BF4B": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "Name": "RestApi" + } + }, + "RestApiDeployment180EC5030503bead3cacf3da667720719058e886": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Description": "Automatically created by the RestApi construct" + }, + "DependsOn": [ + "RestApiproxyANY1786B242", + "RestApiproxyOPTIONS32C4B154", + "RestApiproxyC95856DD", + "RestApiANYA7C1DC94", + "RestApiOPTIONS6AA64D2D" + ], + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W45", + "reason": "ApiGateway has AccessLogging enabled in AWS::ApiGateway::Stage resource, but cfn_nag checkes for it in AWS::ApiGateway::Deployment resource" + } + ] + } + } + }, + "RestApiDeploymentStageprod3855DE66": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "ApiAccessLogGroupCEA70788", + "Arn" + ] + }, + "Format": "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId" + }, + "DeploymentId": { + "Ref": "RestApiDeployment180EC5030503bead3cacf3da667720719058e886" + }, + "MethodSettings": [ + { + "DataTraceEnabled": true, + "HttpMethod": "*", + "LoggingLevel": "INFO", + "ResourcePath": "/*" + } + ], + "StageName": "prod" + } + }, + "RestApiOPTIONS6AA64D2D": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "OPTIONS", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'" + }, + "StatusCode": "204" + } + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }" + }, + "Type": "MOCK" + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Origin": true, + "method.response.header.Access-Control-Allow-Methods": true + }, + "StatusCode": "204" + } + ] + } + }, + "RestApiproxyC95856DD": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "PathPart": "{proxy+}", + "RestApiId": { + "Ref": "RestApi0C43BF4B" + } + } + }, + "RestApiproxyOPTIONS32C4B154": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "OPTIONS", + "ResourceId": { + "Ref": "RestApiproxyC95856DD" + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'", + "method.response.header.Access-Control-Allow-Origin": "'*'", + "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'" + }, + "StatusCode": "204" + } + ], + "RequestTemplates": { + "application/json": "{ statusCode: 200 }" + }, + "Type": "MOCK" + }, + "MethodResponses": [ + { + "ResponseParameters": { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Origin": true, + "method.response.header.Access-Control-Allow-Methods": true + }, + "StatusCode": "204" + } + ] + } + }, + "RestApiproxyANYApiPermissionServerlessBackendStackRestApi00D08F58ANYproxy20E632AA": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANYApiPermissionTestServerlessBackendStackRestApi00D08F58ANYproxyF3BB47AD": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/{proxy+}" + ] + ] + } + } + }, + "RestApiproxyANY1786B242": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Ref": "RestApiproxyC95856DD" + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "CognitoAuthorizer" + }, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiANYApiPermissionServerlessBackendStackRestApi00D08F58ANYEEE63A3B": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/*/" + ] + ] + } + } + }, + "RestApiANYApiPermissionTestServerlessBackendStackRestApi00D08F58ANY9F8B1D52": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "RestApi0C43BF4B" + }, + "/test-invoke-stage/*/" + ] + ] + } + } + }, + "RestApiANYA7C1DC94": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "ANY", + "ResourceId": { + "Fn::GetAtt": [ + "RestApi0C43BF4B", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "AuthorizationType": "COGNITO_USER_POOLS", + "AuthorizerId": { + "Ref": "CognitoAuthorizer" + }, + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "LambdaFunctionBF21E41F", + "Arn" + ] + }, + "/invocations" + ] + ] + } + } + } + }, + "RestApiUsagePlan6E1C537A": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Stage": { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "Throttle": {} + } + ] + } + }, + "ApiAccessLogGroupCEA70788": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "LambdaRestApiCloudWatchRoleF339D4E6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaRestApiCloudWatchRolePolicy" + } + ] + } + }, + "LambdaRestApiAccount": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "LambdaRestApiCloudWatchRoleF339D4E6", + "Arn" + ] + } + }, + "DependsOn": [ + "RestApi0C43BF4B" + ] + }, + "CognitoUserPool53E37E69": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AutoVerifiedAttributes": [ + "email" + ], + "LambdaConfig": {}, + "UserPoolAddOns": { + "AdvancedSecurityMode": "ENFORCED" + }, + "UserPoolName": "WileRydes" + } + }, + "CognitoUserPoolClient5AB59AE4": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "CognitoUserPool53E37E69" + } + } + }, + "CognitoAuthorizer": { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": { + "RestApiId": { + "Ref": "RestApi0C43BF4B" + }, + "Type": "COGNITO_USER_POOLS", + "IdentitySource": "method.request.header.Authorization", + "Name": "authorizer", + "ProviderARNs": [ + { + "Fn::GetAtt": [ + "CognitoUserPool53E37E69", + "Arn" + ] + } + ] + } + }, + "updateConfigHandlerServiceRole3B176B96": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "updateConfigHandlerServiceRoleDefaultPolicy157F28C3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Fn::ImportValue": "websiteBucket" + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "updateConfigHandlerServiceRoleDefaultPolicy157F28C3", + "Roles": [ + { + "Ref": "updateConfigHandlerServiceRole3B176B96" + } + ] + } + }, + "updateConfigHandler59840941": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3Bucket928903EC" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "update_s3_object.on_event", + "Role": { + "Fn::GetAtt": [ + "updateConfigHandlerServiceRole3B176B96", + "Arn" + ] + }, + "Runtime": "python3.8", + "Timeout": 300 + }, + "DependsOn": [ + "updateConfigHandlerServiceRoleDefaultPolicy157F28C3", + "updateConfigHandlerServiceRole3B176B96" + ] + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "updateConfigHandler59840941", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": [ + { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + } + ] + } + }, + "CustomResourceProviderframeworkonEvent0AA4376C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "updateConfigHandler59840941", + "Arn" + ] + } + } + }, + "Timeout": 900 + }, + "DependsOn": [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + ] + }, + "CustomResource": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn" + ] + }, + "UserPool": { + "Ref": "CognitoUserPool53E37E69" + }, + "Client": { + "Ref": "CognitoUserPoolClient5AB59AE4" + }, + "Region": { + "Ref": "AWS::Region" + }, + "Bucket": { + "Fn::ImportValue": "websiteBucket" + }, + "RestApi": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "LambdaToDynamoDBDynamoTable53C1442D": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "RideId", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "RideId", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST", + "SSESpecification": { + "SSEEnabled": true + }, + "TableName": "Rides" + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "Parameters": { + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3Bucket20EEB389": { + "Type": "String", + "Description": "S3 bucket for asset \"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\"" + }, + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360S3VersionKeyC46EC577": { + "Type": "String", + "Description": "S3 key for asset version \"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\"" + }, + "AssetParameters9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360ArtifactHash5ED5576F": { + "Type": "String", + "Description": "Artifact hash for asset \"9a9c398189879e9ca9700ba0658086063d8ee7ccd068043c722c28478c6c4360\"" + }, + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3Bucket928903EC": { + "Type": "String", + "Description": "S3 bucket for asset \"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\"" + }, + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791S3VersionKey3C7BB3DD": { + "Type": "String", + "Description": "S3 key for asset version \"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\"" + }, + "AssetParameters3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791ArtifactHashF30FBC00": { + "Type": "String", + "Description": "Artifact hash for asset \"3aa519f176d0d52023f4992f8ada07849f844467dcb0d4dfb94bb3b350a1d791\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": { + "Type": "String", + "Description": "S3 bucket for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": { + "Type": "String", + "Description": "S3 key for asset version \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": { + "Type": "String", + "Description": "Artifact hash for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + } + }, + "Outputs": { + "RestApiEndpoint0551178A": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "RestApi0C43BF4B" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "RestApiDeploymentStageprod3855DE66" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.ts b/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.ts new file mode 100644 index 000000000..765dd43c4 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/integ.backend-deployment.ts @@ -0,0 +1,18 @@ +/** + * 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 cdk from '@aws-cdk/core'; +import { ServerlessBackendStack } from '../lib/serverless-backend-stack'; + +const app = new cdk.App(); +new ServerlessBackendStack(app, 'ServerlessBackendStack'); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.expected.json b/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.expected.json new file mode 100644 index 000000000..f59d7e0df --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.expected.json @@ -0,0 +1,630 @@ +{ + "Resources": { + "CloudFrontToS3S3LoggingBucketEF5CD8B2": { + "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" + } + ] + } + } + }, + "CloudFrontToS3S3Bucket9CE6AB04": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256" + } + } + ] + }, + "LoggingConfiguration": { + "DestinationBucketName": { + "Ref": "CloudFrontToS3S3LoggingBucketEF5CD8B2" + } + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + }, + "VersioningConfiguration": { + "Status": "Enabled" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "CloudFrontToS3S3BucketPolicy2495300D": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::cloudfront:user/CloudFront Origin Access Identity ", + { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91" + } + ] + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": "s3:GetObject", + "Effect": "Allow", + "Principal": { + "CanonicalUser": { + "Fn::GetAtt": [ + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91", + "S3CanonicalUserId" + ] + } + }, + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "F16", + "reason": "Public website bucket policy requires a wildcard principal" + } + ] + } + } + }, + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B": { + "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 CloudFront Distribution" + }, + { + "id": "W51", + "reason": "This S3 bucket is used as the access logging bucket for CloudFront Distribution" + } + ] + } + } + }, + "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91": { + "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity", + "Properties": { + "CloudFrontOriginAccessIdentityConfig": { + "Comment": "Access S3 bucket content only through CloudFront" + } + } + }, + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Logging": { + "Bucket": { + "Fn::GetAtt": [ + "CloudFrontToS3CloudfrontLoggingBucket8350BE9B", + "RegionalDomainName" + ] + }, + "IncludeCookies": false + }, + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "CloudFrontToS3S3Bucket9CE6AB04", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "S3OriginConfig": { + "OriginAccessIdentity": { + "Fn::Join": [ + "", + [ + "origin-access-identity/cloudfront/", + { + "Ref": "CloudFrontToS3CloudFrontOriginAccessIdentity34CC1F91" + } + ] + ] + } + } + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "staticContentHandlerServiceRole3B648F21": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "staticContentHandlerServiceRoleDefaultPolicy0F5C5865": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*" + ] + }, + { + "Action": [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "staticContentHandlerServiceRoleDefaultPolicy0F5C5865", + "Roles": [ + { + "Ref": "staticContentHandlerServiceRole3B648F21" + } + ] + } + }, + "staticContentHandlerC21DFC88": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3BucketE560DEC2" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "copy_s3_objects.on_event", + "Role": { + "Fn::GetAtt": [ + "staticContentHandlerServiceRole3B648F21", + "Arn" + ] + }, + "Runtime": "python3.8", + "Timeout": 300 + }, + "DependsOn": [ + "staticContentHandlerServiceRoleDefaultPolicy0F5C5865", + "staticContentHandlerServiceRole3B648F21" + ] + }, + "CustomResourceProviderframeworkonEventServiceRole7EBC5835": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "staticContentHandlerC21DFC88", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "Roles": [ + { + "Ref": "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + } + ] + } + }, + "CustomResourceProviderframeworkonEvent0AA4376C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "framework.onEvent", + "Role": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEventServiceRole7EBC5835", + "Arn" + ] + }, + "Runtime": "nodejs10.x", + "Environment": { + "Variables": { + "USER_ON_EVENT_FUNCTION_ARN": { + "Fn::GetAtt": [ + "staticContentHandlerC21DFC88", + "Arn" + ] + } + } + }, + "Timeout": 900 + }, + "DependsOn": [ + "CustomResourceProviderframeworkonEventServiceRoleDefaultPolicy93CD1647", + "CustomResourceProviderframeworkonEventServiceRole7EBC5835" + ] + }, + "CustomResource": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomResourceProviderframeworkonEvent0AA4376C", + "Arn" + ] + }, + "SourceBucket": "wildrydes-us-east-1", + "SourcePrefix": "WebApplication/1_StaticWebHosting/website/", + "Bucket": { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3BucketE560DEC2": { + "Type": "String", + "Description": "S3 bucket for asset \"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\"" + }, + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36S3VersionKeyA9698665": { + "Type": "String", + "Description": "S3 key for asset version \"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\"" + }, + "AssetParameters1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36ArtifactHash171A5ECB": { + "Type": "String", + "Description": "Artifact hash for asset \"1726e5810ad30312b951166bf153fa8cbc793db9019a7fa8f3440a20d21f3d36\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3Bucket6B4B2C9B": { + "Type": "String", + "Description": "S3 bucket for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9S3VersionKey8971BB19": { + "Type": "String", + "Description": "S3 key for asset version \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + }, + "AssetParametersf587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9ArtifactHash72EE40C1": { + "Type": "String", + "Description": "Artifact hash for asset \"f587c683163dea7b70b883fe8f803ffe0622a40e05b3766e08ffa9ed25caabc9\"" + } + }, + "Outputs": { + "websiteURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Fn::GetAtt": [ + "CloudFrontToS3CloudFrontDistributionCFDistribution7EEEEF4E", + "DomainName" + ] + } + ] + ] + } + }, + "websiteBucket": { + "Value": { + "Ref": "CloudFrontToS3S3Bucket9CE6AB04" + }, + "Export": { + "Name": "websiteBucket" + } + } + } +} \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.ts b/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.ts new file mode 100644 index 000000000..6e8a10593 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/integ.s3-static-website-deployment.ts @@ -0,0 +1,18 @@ +/** + * 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 cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; + +const app = new cdk.App(); +new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/test/s3-static-site-stack.test.ts b/source/use_cases/aws-serverless-web-app/test/s3-static-site-stack.test.ts new file mode 100644 index 000000000..acf86147d --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/s3-static-site-stack.test.ts @@ -0,0 +1,113 @@ +/** + * 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 cdk from '@aws-cdk/core'; +import { S3StaticWebsiteStack } from '../lib/s3-static-site-stack'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +test('default stack', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check s3 bucket encryption setting', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::S3::Bucket", { + BucketEncryption: { + ServerSideEncryptionConfiguration: [ + { + ServerSideEncryptionByDefault: { + SSEAlgorithm: "AES256" + } + } + ] + } + }); +}); + +test('check s3 bucket public access setting', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true + } + }); +}); + +test('check CR lambda function permissions', () => { + const app = new cdk.App(); + const stack = new S3StaticWebsiteStack(app, 'S3StaticWebsiteStack'); + expect(stack).toHaveResource("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "s3:GetObject", + "s3:ListBucket" + ], + Effect: "Allow", + Resource: [ + "arn:aws:s3:::wildrydes-us-east-1", + "arn:aws:s3:::wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website/*" + ] + }, + { + Action: [ + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectVersionAcl", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:CopyObject" + ], + Effect: "Allow", + Resource: [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "CloudFrontToS3S3Bucket9CE6AB04" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "CloudFrontToS3S3Bucket9CE6AB04" + }, + "/*" + ] + ] + } + ] + } + ], + Version: "2012-10-17" + } + }); +}); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/test/serverless-backend-stack.test.ts b/source/use_cases/aws-serverless-web-app/test/serverless-backend-stack.test.ts new file mode 100644 index 000000000..5c76a4548 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/test/serverless-backend-stack.test.ts @@ -0,0 +1,79 @@ +/** + * 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 cdk from '@aws-cdk/core'; +import { ServerlessBackendStack } from '../lib/serverless-backend-stack'; +import { SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; + +test('default stack', () => { + const app = new cdk.App(); + const stack = new ServerlessBackendStack(app, 'ServerlessBackendStack'); + expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot(); +}); + +test('check Api Method CORS setting for HTTP OPTIONS method', () => { + const app = new cdk.App(); + const stack = new ServerlessBackendStack(app, 'ServerlessBackendStack'); + expect(stack).toHaveResource("AWS::ApiGateway::Method", { + HttpMethod: "OPTIONS", + AuthorizationType: "NONE", + MethodResponses: [ + { + ResponseParameters: { + "method.response.header.Access-Control-Allow-Headers": true, + "method.response.header.Access-Control-Allow-Origin": true, + "method.response.header.Access-Control-Allow-Methods": true + }, + StatusCode: "204" + } + ] + }); +}); + +test('check lambda permissions', () => { + const app = new cdk.App(); + const stack = new ServerlessBackendStack(app, 'ServerlessBackendStack'); + expect(stack).toHaveResource("AWS::Lambda::Permission", { + Action: "lambda:InvokeFunction", + Principal: "apigateway.amazonaws.com", + SourceArn: { + "Fn::Join": [ + "", + [ + "arn:", + { + Ref: "AWS::Partition" + }, + ":execute-api:", + { + Ref: "AWS::Region" + }, + ":", + { + Ref: "AWS::AccountId" + }, + ":", + { + Ref: "RestApi0C43BF4B" + }, + "/", + { + Ref: "RestApiDeploymentStageprod3855DE66" + }, + "/*/" + ] + ] + } + }); +}); \ No newline at end of file diff --git a/source/use_cases/aws-serverless-web-app/tsconfig.json b/source/use_cases/aws-serverless-web-app/tsconfig.json new file mode 100644 index 000000000..10250a8e6 --- /dev/null +++ b/source/use_cases/aws-serverless-web-app/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ "es2018" ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "stripInternal": true, + "target": "ES2018" + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules","cdk.out" + ] +} diff --git a/source/use_cases/eslintrc.yml b/source/use_cases/eslintrc.yml new file mode 100644 index 000000000..6806f7be5 --- /dev/null +++ b/source/use_cases/eslintrc.yml @@ -0,0 +1,49 @@ +--- +env: + jest: true + node: true + +plugins: + - '@typescript-eslint' + - import + - license-header + +parser: '@typescript-eslint/parser' +parserOptions: + ecmaVersion: 2018 + sourceType: module + project: ./tsconfig.json + +extends: + - plugin:import/typescript + +settings: + import/parsers: + '@typescript-eslint/parser': ['.ts', '.tsx'] + import/resolver: + node: {} + typescript: + directory: ./tsconfig.json + +rules: + # Require use of the `import { foo } from 'bar';` form instead of `import foo = require('bar');` + '@typescript-eslint/no-require-imports': + - error + + # Require all imported dependencies are actually declared in package.json + 'import/no-extraneous-dependencies': + - error + - devDependencies: # Only allow importing devDependencies from: + - '**/test/**' # --> Unit tests + - '**/utils.ts' # --> uses deepmerge + optionalDependencies: false # Disallow importing optional dependencies (those shouldn't be in use in the project) + peerDependencies: false # Disallow importing peer dependencies (that aren't also direct dependencies) + + # Require all imported libraries actually resolve (!!required for import/no-extraneous-dependencies to work!!) + 'import/no-unresolved': + - error + + #Check for license header + 'license-header/header': + - error + - ../license-header.js diff --git a/source/use_cases/license-header.js b/source/use_cases/license-header.js new file mode 100644 index 000000000..e3d94bb9f --- /dev/null +++ b/source/use_cases/license-header.js @@ -0,0 +1,12 @@ +/** + * 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. + */ \ No newline at end of file