diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000..f9be6c5871dfa --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ +--- +name: New Issue +labels: needs-triage +--- + +**Note: for support questions, please first reference our [documentation](https://docs.aws.amazon.com/cdk/api/latest), then use [Stackoverflow](https://stackoverflow.com/questions/ask?tags=aws-cdk)**. This repository's issues are intended for feature requests and bug reports. + +* **I'm submitting a ...** + - [ ] :beetle: bug report + - [ ] :rocket: feature request + - [ ] :books: construct library gap + - [ ] :phone: security issue or vulnerability => Please see [policy](https://github.com/awslabs/aws-cdk/security/policy) + - [ ] :question: support request => Please see note at the top of this template. + + +* **What is the current behavior?** +*If the current behavior is a :beetle:bug:beetle:: Please provide the steps to reproduce* + + + +* **What is the expected behavior (or behavior of feature suggested)?** + + + +* **What is the motivation / use case for changing the behavior or adding this feature?** + + + +* **Please tell us about your environment:** + + - CDK CLI Version: xx.xx.xx + - Module Version: xx.xx.xx + - OS: [all | Windows 10 | OSX Mojave | Ubuntu | etc... ] + - Language: [all | TypeScript | Java | Python ] + + +* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. associated pull-request, stackoverflow, gitter, etc) + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 2dd42f3245ba7..0000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Bug report -about: The software behaves in an unexpected way -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Command line used or code snippet - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Version:** - - OS - - Programming Language - - CDK Version diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 420c748d7867a..0000000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Feature request -about: Suggest an idea -title: '' -labels: feature-request -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md b/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md deleted file mode 100644 index 71e3cfeaaf39f..0000000000000 --- a/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Report a gap in the AWS Construct Library -about: Missing feature in the AWS Construct Library -title: '' -labels: gap -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md b/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md deleted file mode 100644 index a0af80bd9e1be..0000000000000 --- a/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: Request sample or ask a question -about: Not sure how -title: '' -labels: '' -assignees: '' - ---- - -Check out the [CDK User Guide][1] or ask a question on [Stack Overflow][2] - ---thanks, The CDK Team - -[1]: https://docs.aws.amazon.com/CDK/latest/userguide -[2]: https://stackoverflow.com/questions/ask?tags=aws-cdk diff --git a/NOTICE b/NOTICE index 95fd48569c743..bf61ddbe53e78 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,3 @@ AWS Cloud Development Kit (AWS CDK) Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index d8222699ecd8b..bc8cbf9dbafa7 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -63,9 +63,9 @@ export interface PipelineDeployStackActionProps { * information * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities - * @default AnonymousIAM, unless `adminPermissions` is true + * @default [AnonymousIAM, AutoExpand], unless `adminPermissions` is true */ - readonly capabilities?: cfn.CloudFormationCapabilities; + readonly capabilities?: cfn.CloudFormationCapabilities[]; /** * Whether to grant admin permissions to CloudFormation while deploying this template. @@ -166,13 +166,13 @@ export class PipelineDeployStackAction extends cdk.Construct { } } -function cfnCapabilities(adminPermissions: boolean, capabilities?: cfn.CloudFormationCapabilities): cfn.CloudFormationCapabilities { +function cfnCapabilities(adminPermissions: boolean, capabilities?: cfn.CloudFormationCapabilities[]): cfn.CloudFormationCapabilities[] { if (adminPermissions && capabilities === undefined) { - // admin true default capability to NamedIAM - return cfn.CloudFormationCapabilities.NamedIAM; + // admin true default capability to NamedIAM and AutoExpand + return [cfn.CloudFormationCapabilities.NamedIAM, cfn.CloudFormationCapabilities.AutoExpand]; } else if (capabilities === undefined) { - // else capabilities are undefined set AnonymousIAM - return cfn.CloudFormationCapabilities.AnonymousIAM; + // else capabilities are undefined set AnonymousIAM and AutoExpand + return [cfn.CloudFormationCapabilities.AnonymousIAM, cfn.CloudFormationCapabilities.AutoExpand]; } else { // else capabilities are defined use them return capabilities; diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts index 7f58a8ca49164..ed56805675c1f 100644 --- a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts +++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts @@ -35,7 +35,7 @@ new cicd.PipelineDeployStackAction(stack, 'DeployStack', { executeChangeSetRunOrder: 999, input: sourceOutput, adminPermissions: false, - capabilities: cfn.CloudFormationCapabilities.None, + capabilities: [cfn.CloudFormationCapabilities.None], }); app.synth(); diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 1da595f6aba91..e97deed370e38 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -86,32 +86,55 @@ export = nodeunit.testCase({ const stackWithAnonymousCapability = new cdk.Stack(undefined, 'AnonymousIAM', { env: { account: '123456789012', region: 'us-east-1' } }); + const stackWithAutoExpandCapability = new cdk.Stack(undefined, 'AutoExpand', + { env: { account: '123456789012', region: 'us-east-1' } }); + + const stackWithAnonymousAndAutoExpandCapability = new cdk.Stack(undefined, 'AnonymousIAMAndAutoExpand', + { env: { account: '123456789012', region: 'us-east-1' } }); + const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); const pipeline = selfUpdatingStack.pipeline; + const selfUpdateStage1 = pipeline.addStage({ stageName: 'SelfUpdate1' }); const selfUpdateStage2 = pipeline.addStage({ stageName: 'SelfUpdate2' }); const selfUpdateStage3 = pipeline.addStage({ stageName: 'SelfUpdate3' }); + const selfUpdateStage4 = pipeline.addStage({ stageName: 'SelfUpdate4' }); + const selfUpdateStage5 = pipeline.addStage({ stageName: 'SelfUpdate5' }); new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage1, stack: pipelineStack, input: selfUpdatingStack.synthesizedApp, - capabilities: cfn.CloudFormationCapabilities.NamedIAM, + capabilities: [cfn.CloudFormationCapabilities.NamedIAM], adminPermissions: false, }); new PipelineDeployStackAction(pipelineStack, 'DeployStack', { stage: selfUpdateStage2, stack: stackWithNoCapability, input: selfUpdatingStack.synthesizedApp, - capabilities: cfn.CloudFormationCapabilities.None, + capabilities: [cfn.CloudFormationCapabilities.None], adminPermissions: false, }); new PipelineDeployStackAction(pipelineStack, 'DeployStack2', { stage: selfUpdateStage3, stack: stackWithAnonymousCapability, input: selfUpdatingStack.synthesizedApp, - capabilities: cfn.CloudFormationCapabilities.AnonymousIAM, + capabilities: [cfn.CloudFormationCapabilities.AnonymousIAM], + adminPermissions: false, + }); + new PipelineDeployStackAction(pipelineStack, 'DeployStack3', { + stage: selfUpdateStage4, + stack: stackWithAutoExpandCapability, + input: selfUpdatingStack.synthesizedApp, + capabilities: [cfn.CloudFormationCapabilities.AutoExpand], + adminPermissions: false, + }); + new PipelineDeployStackAction(pipelineStack, 'DeployStack4', { + stage: selfUpdateStage5, + stack: stackWithAnonymousAndAutoExpandCapability, + input: selfUpdatingStack.synthesizedApp, + capabilities: [cfn.CloudFormationCapabilities.AnonymousIAM, cfn.CloudFormationCapabilities.AutoExpand], adminPermissions: false, }); expect(pipelineStack).to(haveResource('AWS::CodePipeline::Pipeline', hasPipelineAction({ @@ -148,6 +171,20 @@ export = nodeunit.testCase({ ActionMode: "CHANGE_SET_REPLACE", } }))); + expect(pipelineStack).to(haveResource('AWS::CodePipeline::Pipeline', hasPipelineAction({ + Configuration: { + StackName: "AutoExpand", + ActionMode: "CHANGE_SET_REPLACE", + Capabilities: "CAPABILITY_AUTO_EXPAND", + } + }))); + expect(pipelineStack).to(haveResource('AWS::CodePipeline::Pipeline', hasPipelineAction({ + Configuration: { + StackName: "AnonymousIAMAndAutoExpand", + ActionMode: "CHANGE_SET_REPLACE", + Capabilities: "CAPABILITY_IAM,CAPABILITY_AUTO_EXPAND", + } + }))); test.done(); }, 'users can use admin permissions'(test: nodeunit.Test) { @@ -178,7 +215,7 @@ export = nodeunit.testCase({ Configuration: { StackName: "TestStack", ActionMode: "CHANGE_SET_REPLACE", - Capabilities: "CAPABILITY_NAMED_IAM", + Capabilities: "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", } }))); test.done(); @@ -307,7 +344,7 @@ class FakeAction extends codepipeline.Action { super({ actionName, artifactBounds: { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, - category: codepipeline.ActionCategory.Test, + category: codepipeline.ActionCategory.TEST, provider: 'Test', }); diff --git a/packages/@aws-cdk/assert/package-lock.json b/packages/@aws-cdk/assert/package-lock.json index 929f7175f543a..473a5d0f04d48 100644 --- a/packages/@aws-cdk/assert/package-lock.json +++ b/packages/@aws-cdk/assert/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/assets-docker/.gitignore b/packages/@aws-cdk/assets-docker/.gitignore deleted file mode 100644 index 2a7ab813ef1bf..0000000000000 --- a/packages/@aws-cdk/assets-docker/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -*.js -*.js.map -*.d.ts -node_modules -dist -tsconfig.json -tslint.json - -.LAST_BUILD -.nyc_output -coverage - -.jsii - -.nycrc -.LAST_PACKAGE -*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/.jsii b/packages/@aws-cdk/assets-docker/.jsii new file mode 100644 index 0000000000000..66fb4b8beaefd --- /dev/null +++ b/packages/@aws-cdk/assets-docker/.jsii @@ -0,0 +1,843 @@ +{ + "author": { + "name": "Amazon Web Services", + "organization": true, + "roles": [ + "author" + ], + "url": "https://aws.amazon.com" + }, + "dependencies": { + "@aws-cdk/assets": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.Assets", + "packageId": "Amazon.CDK.Assets", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-assets", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.assets" + }, + "js": { + "npm": "@aws-cdk/assets" + }, + "python": { + "distName": "aws-cdk.assets", + "module": "aws_cdk.assets" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-cloudformation": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.CloudFormation", + "packageId": "Amazon.CDK.AWS.CloudFormation", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cloudformation", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.cloudformation" + }, + "js": { + "npm": "@aws-cdk/aws-cloudformation" + }, + "python": { + "distName": "aws-cdk.aws-cloudformation", + "module": "aws_cdk.aws_cloudformation" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-ecr": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.ECR", + "packageId": "Amazon.CDK.AWS.ECR", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "ecr", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.ecr" + }, + "js": { + "npm": "@aws-cdk/aws-ecr" + }, + "python": { + "distName": "aws-cdk.aws-ecr", + "module": "aws_cdk.aws_ecr" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-iam": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.IAM", + "packageId": "Amazon.CDK.AWS.IAM", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "iam", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + }, + "python": { + "distName": "aws-cdk.aws-iam", + "module": "aws_cdk.aws_iam" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-lambda": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.Lambda", + "packageId": "Amazon.CDK.AWS.Lambda", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "lambda", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.lambda" + }, + "js": { + "npm": "@aws-cdk/aws-lambda" + }, + "python": { + "distName": "aws-cdk.aws-lambda", + "module": "aws_cdk.aws_lambda" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-s3": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.S3", + "packageId": "Amazon.CDK.AWS.S3", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "s3", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + }, + "python": { + "distName": "aws-cdk.aws-s3", + "module": "aws_cdk.aws_s3" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/cdk": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK", + "packageId": "Amazon.CDK", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + }, + "python": { + "distName": "aws-cdk.cdk", + "module": "aws_cdk.cdk" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.CXAPI", + "packageId": "Amazon.CDK.CXAPI", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-cx-api", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + }, + "python": { + "distName": "aws-cdk.cx-api", + "module": "aws_cdk.cx_api" + } + }, + "version": "0.34.0" + } + }, + "dependencyClosure": { + "@aws-cdk/assets": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.Assets", + "packageId": "Amazon.CDK.Assets", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-assets", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.assets" + }, + "js": { + "npm": "@aws-cdk/assets" + }, + "python": { + "distName": "aws-cdk.assets", + "module": "aws_cdk.assets" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-cloudformation": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.CloudFormation", + "packageId": "Amazon.CDK.AWS.CloudFormation", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cloudformation", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.cloudformation" + }, + "js": { + "npm": "@aws-cdk/aws-cloudformation" + }, + "python": { + "distName": "aws-cdk.aws-cloudformation", + "module": "aws_cdk.aws_cloudformation" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-cloudwatch": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.CloudWatch", + "packageId": "Amazon.CDK.AWS.CloudWatch", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cloudwatch", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.cloudwatch" + }, + "js": { + "npm": "@aws-cdk/aws-cloudwatch" + }, + "python": { + "distName": "aws-cdk.aws-cloudwatch", + "module": "aws_cdk.aws_cloudwatch" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-ec2": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.EC2", + "packageId": "Amazon.CDK.AWS.EC2", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "ec2", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.ec2" + }, + "js": { + "npm": "@aws-cdk/aws-ec2" + }, + "python": { + "distName": "aws-cdk.aws-ec2", + "module": "aws_cdk.aws_ec2" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-ecr": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.ECR", + "packageId": "Amazon.CDK.AWS.ECR", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "ecr", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.ecr" + }, + "js": { + "npm": "@aws-cdk/aws-ecr" + }, + "python": { + "distName": "aws-cdk.aws-ecr", + "module": "aws_cdk.aws_ecr" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-events": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.Events", + "packageId": "Amazon.CDK.AWS.Events", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "events", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.events" + }, + "js": { + "npm": "@aws-cdk/aws-events" + }, + "python": { + "distName": "aws-cdk.aws-events", + "module": "aws_cdk.aws_events" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-iam": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.IAM", + "packageId": "Amazon.CDK.AWS.IAM", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "iam", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.iam" + }, + "js": { + "npm": "@aws-cdk/aws-iam" + }, + "python": { + "distName": "aws-cdk.aws-iam", + "module": "aws_cdk.aws_iam" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-kms": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.KMS", + "packageId": "Amazon.CDK.AWS.KMS", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "kms", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.kms" + }, + "js": { + "npm": "@aws-cdk/aws-kms" + }, + "python": { + "distName": "aws-cdk.aws-kms", + "module": "aws_cdk.aws_kms" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-lambda": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.Lambda", + "packageId": "Amazon.CDK.AWS.Lambda", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "lambda", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.lambda" + }, + "js": { + "npm": "@aws-cdk/aws-lambda" + }, + "python": { + "distName": "aws-cdk.aws-lambda", + "module": "aws_cdk.aws_lambda" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-logs": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.Logs", + "packageId": "Amazon.CDK.AWS.Logs", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "logs", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.logs" + }, + "js": { + "npm": "@aws-cdk/aws-logs" + }, + "python": { + "distName": "aws-cdk.aws-logs", + "module": "aws_cdk.aws_logs" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-s3": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.S3", + "packageId": "Amazon.CDK.AWS.S3", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "s3", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.s3" + }, + "js": { + "npm": "@aws-cdk/aws-s3" + }, + "python": { + "distName": "aws-cdk.aws-s3", + "module": "aws_cdk.aws_s3" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-sns": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.SNS", + "packageId": "Amazon.CDK.AWS.SNS", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "sns", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.sns" + }, + "js": { + "npm": "@aws-cdk/aws-sns" + }, + "python": { + "distName": "aws-cdk.aws-sns", + "module": "aws_cdk.aws_sns" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/aws-sqs": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.SQS", + "packageId": "Amazon.CDK.AWS.SQS", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "sqs", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.sqs" + }, + "js": { + "npm": "@aws-cdk/aws-sqs" + }, + "python": { + "distName": "aws-cdk.aws-sqs", + "module": "aws_cdk.aws_sqs" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/cdk": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK", + "packageId": "Amazon.CDK", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk" + }, + "js": { + "npm": "@aws-cdk/cdk" + }, + "python": { + "distName": "aws-cdk.cdk", + "module": "aws_cdk.cdk" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/cx-api": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.CXAPI", + "packageId": "Amazon.CDK.CXAPI", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-cx-api", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.cxapi" + }, + "js": { + "npm": "@aws-cdk/cx-api" + }, + "python": { + "distName": "aws-cdk.cx-api", + "module": "aws_cdk.cx_api" + } + }, + "version": "0.34.0" + }, + "@aws-cdk/region-info": { + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.RegionInfo", + "packageId": "Amazon.CDK.RegionInfo", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-region-info", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.regioninfo" + }, + "js": { + "npm": "@aws-cdk/region-info" + }, + "python": { + "distName": "aws-cdk.region-info", + "module": "aws_cdk.region_info" + } + }, + "version": "0.34.0" + } + }, + "description": "Docker image assets", + "docs": { + "stability": "experimental" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "jsiiVersion": "0.11.2 (build 27d16c2)", + "license": "Apache-2.0", + "name": "@aws-cdk/assets-docker", + "readme": { + "markdown": "# AWS CDK Docker Image Assets\n\n\n---\n\n![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)\n\n> This API is still under active development and subject to non-backward\n> compatible changes or removal in any future version. Use of the API is not recommended in production\n> environments. Experimental APIs are not subject to the Semantic Versioning model.\n\n---\n\n\nThis module allows bundling Docker images as assets.\n\nImages are built from a local Docker context directory (with a `Dockerfile`),\nuploaded to ECR by the CDK toolkit and/or your app's CI-CD pipeline, and can be\nnaturally referenced in your CDK app.\n\n```typescript\nimport { DockerImageAsset } from '@aws-cdk/assets-docker';\n\nconst asset = new DockerImageAsset(this, 'MyBuildImage', {\n directory: path.join(__dirname, 'my-image')\n});\n```\n\nThe directory `my-image` must include a `Dockerfile`.\n\nThis will instruct the toolkit to build a Docker image from `my-image`, push it\nto an AWS ECR repository and wire the name of the repository as CloudFormation\nparameters to your stack.\n\nUse `asset.imageUri` to reference the image (it includes both the ECR image URL\nand tag.\n\nYou can optionally pass build args to the `docker build` command by specifying\nthe `buildArgs` property:\n\n```typescript\nconst asset = new DockerImageAsset(this, 'MyBuildImage', {\n directory: path.join(__dirname, 'my-image'),\n buildArgs: {\n HTTP_PROXY: 'http://10.20.30.2:1234'\n }\n});\n```\n\n### Pull Permissions\n\nDepending on the consumer of your image asset, you will need to make sure\nthe principal has permissions to pull the image.\n\nIn most cases, you should use the `asset.repository.grantPull(principal)`\nmethod. This will modify the IAM policy of the principal to allow it to\npull images from this repository.\n\nIf the pulling principal is not in the same account or is an AWS service that\ndoesn't assume a role in your account (e.g. AWS CodeBuild), pull permissions\nmust be granted on the __resource policy__ (and not on the principal's policy).\nTo do that, you can use `asset.repository.addToResourcePolicy(statement)` to\ngrant the desired principal the following permissions: \"ecr:GetDownloadUrlForLayer\",\n\"ecr:BatchGetImage\" and \"ecr:BatchCheckLayerAvailability\".\n" + }, + "repository": { + "directory": "packages/@aws-cdk/assets-docker", + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "schema": "jsii/0.10.0", + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.Assets.Docker", + "packageId": "Amazon.CDK.Assets.Docker", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-assets-docker", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.assets.docker" + }, + "js": { + "npm": "@aws-cdk/assets-docker" + }, + "python": { + "distName": "aws-cdk.assets-docker", + "module": "aws_cdk.assets_docker" + } + }, + "types": { + "@aws-cdk/assets-docker.DockerImageAsset": { + "assembly": "@aws-cdk/assets-docker", + "base": "@aws-cdk/cdk.Construct", + "docs": { + "remarks": "The image will be created in build time and uploaded to an ECR repository.", + "stability": "experimental", + "summary": "An asset that represents a Docker image." + }, + "fqn": "@aws-cdk/assets-docker.DockerImageAsset", + "initializer": { + "docs": { + "stability": "experimental" + }, + "parameters": [ + { + "name": "scope", + "type": { + "fqn": "@aws-cdk/cdk.Construct" + } + }, + { + "name": "id", + "type": { + "primitive": "string" + } + }, + { + "name": "props", + "type": { + "fqn": "@aws-cdk/assets-docker.DockerImageAssetProps" + } + } + ] + }, + "interfaces": [ + "@aws-cdk/assets.IAsset" + ], + "kind": "class", + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 39 + }, + "name": "DockerImageAsset", + "properties": [ + { + "docs": { + "remarks": "As this is\na late-bound token, it may not be used in construct IDs, but can be passed as a resource\nproperty in order to force a change on a resource when an asset is effectively updated. This is\nmore reliable than `sourceHash` in particular for assets which bundling phase involve external\nresources that can change over time (such as Docker image builds).", + "stability": "experimental", + "summary": "A hash of the bundle for of this asset, which is only available at deployment time." + }, + "immutable": true, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 52 + }, + "name": "artifactHash", + "overrides": "@aws-cdk/assets.IAsset", + "type": { + "primitive": "string" + } + }, + { + "docs": { + "remarks": "As this is a plain\nstring, it can be used in construct IDs in order to enforce creation of a new resource when\nthe content hash has changed.", + "stability": "experimental", + "summary": "A hash of the source of this asset, which is available at construction time." + }, + "immutable": true, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 51 + }, + "name": "sourceHash", + "overrides": "@aws-cdk/assets.IAsset", + "type": { + "primitive": "string" + } + }, + { + "docs": { + "remarks": "Use this reference to pull\nthe asset.", + "stability": "experimental", + "summary": "The full URI of the image (including a tag)." + }, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 44 + }, + "name": "imageUri", + "type": { + "primitive": "string" + } + }, + { + "docs": { + "stability": "experimental", + "summary": "Repository where the image is stored." + }, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 49 + }, + "name": "repository", + "type": { + "fqn": "@aws-cdk/aws-ecr.IRepository" + } + } + ] + }, + "@aws-cdk/assets-docker.DockerImageAssetProps": { + "assembly": "@aws-cdk/assets-docker", + "datatype": true, + "docs": { + "stability": "experimental" + }, + "fqn": "@aws-cdk/assets-docker.DockerImageAssetProps", + "interfaces": [ + "@aws-cdk/assets.CopyOptions" + ], + "kind": "interface", + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 9 + }, + "name": "DockerImageAssetProps", + "properties": [ + { + "abstract": true, + "docs": { + "stability": "experimental", + "summary": "The directory where the Dockerfile is stored." + }, + "immutable": true, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 13 + }, + "name": "directory", + "type": { + "primitive": "string" + } + }, + { + "abstract": true, + "docs": { + "default": "no build args are passed", + "stability": "experimental", + "summary": "Build args to pass to the `docker build` command." + }, + "immutable": true, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 31 + }, + "name": "buildArgs", + "optional": true, + "type": { + "collection": { + "elementtype": { + "primitive": "string" + }, + "kind": "map" + } + } + }, + { + "abstract": true, + "docs": { + "default": "automatically derived from the asset's ID.", + "remarks": "Specify this property if you need to statically address the image, e.g.\nfrom a Kubernetes Pod. Note, this is only the repository name, without the\nregistry and the tag parts.", + "stability": "experimental", + "summary": "ECR repository name." + }, + "immutable": true, + "locationInModule": { + "filename": "lib/image-asset.ts", + "line": 24 + }, + "name": "repositoryName", + "optional": true, + "type": { + "primitive": "string" + } + } + ] + } + }, + "version": "0.34.0", + "fingerprint": "acMLPBNLALu+vA/4uVagzUf/VrUs5fcsT0ZC1+R3WoA=" +} diff --git a/packages/@aws-cdk/assets-docker/.npmignore b/packages/@aws-cdk/assets-docker/.npmignore deleted file mode 100644 index f5a63a96df103..0000000000000 --- a/packages/@aws-cdk/assets-docker/.npmignore +++ /dev/null @@ -1,18 +0,0 @@ -# Don't include original .ts files when doing `npm pack` -*.ts -!*.d.ts -coverage -.nyc_output -*.tgz - -dist -.LAST_PACKAGE -.LAST_BUILD -!*.js - -# Include .jsii -!.jsii - -*.snk - -*.tsbuildinfo diff --git a/packages/@aws-cdk/assets-docker/.nycrc b/packages/@aws-cdk/assets-docker/.nycrc new file mode 120000 index 0000000000000..5ad6c1a112fa6 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/.nycrc @@ -0,0 +1 @@ +/Users/spgingras/aws-cdk/tools/cdk-build-tools/config/nycrc \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/LICENSE b/packages/@aws-cdk/assets-docker/LICENSE deleted file mode 100644 index 46c185646b439..0000000000000 --- a/packages/@aws-cdk/assets-docker/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2018-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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/@aws-cdk/assets-docker/NOTICE b/packages/@aws-cdk/assets-docker/NOTICE deleted file mode 100644 index 8585168af8b7d..0000000000000 --- a/packages/@aws-cdk/assets-docker/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ -AWS Cloud Development Kit (AWS CDK) -Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/assets-docker/README.md b/packages/@aws-cdk/assets-docker/README.md deleted file mode 100644 index 59b0515a0172f..0000000000000 --- a/packages/@aws-cdk/assets-docker/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# AWS CDK Docker Image Assets - - ---- - -![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge) - -> This API may emit warnings. Backward compatibility is not guaranteed. - ---- - - -Deprecated: @aws-cdk/aws-ecr-assets \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/lib/adopted-repository.d.ts b/packages/@aws-cdk/assets-docker/lib/adopted-repository.d.ts new file mode 100644 index 0000000000000..c7f997f05743c --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/adopted-repository.d.ts @@ -0,0 +1,39 @@ +import ecr = require('@aws-cdk/aws-ecr'); +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +interface AdoptedRepositoryProps { + /** + * An ECR repository to adopt. Once adopted, the repository will + * practically become part of this stack, so it will be removed when + * the stack is deleted. + */ + repositoryName: string; +} +/** + * An internal class used to adopt an ECR repository used for the locally built + * image into the stack. + * + * Since the repository is not created by the stack (but by the CDK toolkit), + * adopting will make the repository "owned" by the stack. It will be cleaned + * up when the stack gets deleted, to avoid leaving orphaned repositories on + * stack cleanup. + */ +export declare class AdoptedRepository extends ecr.RepositoryBase { + private readonly props; + readonly repositoryName: string; + readonly repositoryArn: string; + private readonly policyDocument; + constructor(scope: cdk.Construct, id: string, props: AdoptedRepositoryProps); + /** + * Export this repository from the stack + */ + export(): AdoptedRepositoryProps; + /** + * Adds a statement to the repository resource policy. + * + * Contrary to normal imported repositories, which no-op here, we can + * use the custom resource to modify the ECR resource policy if needed. + */ + addToResourcePolicy(statement: iam.PolicyStatement): void; +} +export {}; diff --git a/packages/@aws-cdk/assets-docker/lib/adopted-repository.js b/packages/@aws-cdk/assets-docker/lib/adopted-repository.js new file mode 100644 index 0000000000000..107d9b9cb90d8 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/adopted-repository.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const cfn = require("@aws-cdk/aws-cloudformation"); +const ecr = require("@aws-cdk/aws-ecr"); +const iam = require("@aws-cdk/aws-iam"); +const lambda = require("@aws-cdk/aws-lambda"); +const path = require("path"); +/** + * An internal class used to adopt an ECR repository used for the locally built + * image into the stack. + * + * Since the repository is not created by the stack (but by the CDK toolkit), + * adopting will make the repository "owned" by the stack. It will be cleaned + * up when the stack gets deleted, to avoid leaving orphaned repositories on + * stack cleanup. + */ +class AdoptedRepository extends ecr.RepositoryBase { + constructor(scope, id, props) { + super(scope, id); + this.props = props; + this.policyDocument = new iam.PolicyDocument(); + const fn = new lambda.SingletonFunction(this, 'Function', { + runtime: lambda.Runtime.Nodejs810, + lambdaPurpose: 'AdoptEcrRepository', + handler: 'handler.handler', + code: lambda.Code.asset(path.join(__dirname, 'adopt-repository')), + uuid: 'dbc60def-c595-44bc-aa5c-28c95d68f62c', + timeout: 300 + }); + fn.addToRolePolicy(new iam.PolicyStatement() + .addResource(ecr.Repository.arnForLocalRepository(props.repositoryName, this)) + .addActions('ecr:GetRepositoryPolicy', 'ecr:SetRepositoryPolicy', 'ecr:DeleteRepository', 'ecr:ListImages', 'ecr:BatchDeleteImage')); + const adopter = new cfn.CustomResource(this, 'Resource', { + resourceType: 'Custom::ECRAdoptedRepository', + provider: cfn.CustomResourceProvider.lambda(fn), + properties: { + RepositoryName: props.repositoryName, + PolicyDocument: this.policyDocument + } + }); + if (fn.role) { + // Need to explicitly depend on the role's policies, so they are applied before we try to use them + adopter.node.addDependency(fn.role); + } + // we use the Fn::GetAtt with the RepositoryName returned by the custom + // resource in order to implicitly create a dependency between consumers + // and the custom resource. + this.repositoryName = adopter.getAtt('RepositoryName').toString(); + // this this repository is "local" to the stack (in the same region/account) + // we can render it's ARN from it's name. + this.repositoryArn = ecr.Repository.arnForLocalRepository(this.repositoryName, this); + } + /** + * Export this repository from the stack + */ + export() { + return this.props; + } + /** + * Adds a statement to the repository resource policy. + * + * Contrary to normal imported repositories, which no-op here, we can + * use the custom resource to modify the ECR resource policy if needed. + */ + addToResourcePolicy(statement) { + this.policyDocument.addStatement(statement); + } +} +exports.AdoptedRepository = AdoptedRepository; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRvcHRlZC1yZXBvc2l0b3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYWRvcHRlZC1yZXBvc2l0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBQW9EO0FBQ3BELHdDQUF5QztBQUN6Qyx3Q0FBeUM7QUFDekMsOENBQStDO0FBRS9DLDZCQUE4QjtBQVc5Qjs7Ozs7Ozs7R0FRRztBQUNILE1BQWEsaUJBQWtCLFNBQVEsR0FBRyxDQUFDLGNBQWM7SUFNdkQsWUFBWSxLQUFvQixFQUFFLEVBQVUsRUFBbUIsS0FBNkI7UUFDMUYsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUQ0QyxVQUFLLEdBQUwsS0FBSyxDQUF3QjtRQUYzRSxtQkFBYyxHQUFHLElBQUksR0FBRyxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBS3pELE1BQU0sRUFBRSxHQUFHLElBQUksTUFBTSxDQUFDLGlCQUFpQixDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDeEQsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPLENBQUMsU0FBUztZQUNqQyxhQUFhLEVBQUUsb0JBQW9CO1lBQ25DLE9BQU8sRUFBRSxpQkFBaUI7WUFDMUIsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLGtCQUFrQixDQUFDLENBQUM7WUFDakUsSUFBSSxFQUFFLHNDQUFzQztZQUM1QyxPQUFPLEVBQUUsR0FBRztTQUNiLENBQUMsQ0FBQztRQUVILEVBQUUsQ0FBQyxlQUFlLENBQUMsSUFBSSxHQUFHLENBQUMsZUFBZSxFQUFFO2FBQ3pDLFdBQVcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLHFCQUFxQixDQUFDLEtBQUssQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDN0UsVUFBVSxDQUNULHlCQUF5QixFQUN6Qix5QkFBeUIsRUFDekIsc0JBQXNCLEVBQ3RCLGdCQUFnQixFQUNoQixzQkFBc0IsQ0FDdkIsQ0FBQyxDQUFDO1FBRUwsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDdkQsWUFBWSxFQUFFLDhCQUE4QjtZQUM1QyxRQUFRLEVBQUUsR0FBRyxDQUFDLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7WUFDL0MsVUFBVSxFQUFFO2dCQUNWLGNBQWMsRUFBRSxLQUFLLENBQUMsY0FBYztnQkFDcEMsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjO2FBQ3BDO1NBQ0YsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxFQUFFLENBQUMsSUFBSSxFQUFFO1lBQ1gsa0dBQWtHO1lBQ2xHLE9BQU8sQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUNyQztRQUVELHVFQUF1RTtRQUN2RSx3RUFBd0U7UUFDeEUsMkJBQTJCO1FBQzNCLElBQUksQ0FBQyxjQUFjLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRWxFLDRFQUE0RTtRQUM1RSx5Q0FBeUM7UUFDekMsSUFBSSxDQUFDLGFBQWEsR0FBRyxHQUFHLENBQUMsVUFBVSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdkYsQ0FBQztJQUVEOztPQUVHO0lBQ0ksTUFBTTtRQUNYLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQztJQUNwQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxtQkFBbUIsQ0FBQyxTQUE4QjtRQUN2RCxJQUFJLENBQUMsY0FBYyxDQUFDLFlBQVksQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUM5QyxDQUFDO0NBQ0Y7QUFuRUQsOENBbUVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNmbiA9IHJlcXVpcmUoJ0Bhd3MtY2RrL2F3cy1jbG91ZGZvcm1hdGlvbicpO1xuaW1wb3J0IGVjciA9IHJlcXVpcmUoJ0Bhd3MtY2RrL2F3cy1lY3InKTtcbmltcG9ydCBpYW0gPSByZXF1aXJlKCdAYXdzLWNkay9hd3MtaWFtJyk7XG5pbXBvcnQgbGFtYmRhID0gcmVxdWlyZSgnQGF3cy1jZGsvYXdzLWxhbWJkYScpO1xuaW1wb3J0IGNkayA9IHJlcXVpcmUoJ0Bhd3MtY2RrL2NkaycpO1xuaW1wb3J0IHBhdGggPSByZXF1aXJlKCdwYXRoJyk7XG5cbmludGVyZmFjZSBBZG9wdGVkUmVwb3NpdG9yeVByb3BzIHtcbiAgLyoqXG4gICAqIEFuIEVDUiByZXBvc2l0b3J5IHRvIGFkb3B0LiBPbmNlIGFkb3B0ZWQsIHRoZSByZXBvc2l0b3J5IHdpbGxcbiAgICogcHJhY3RpY2FsbHkgYmVjb21lIHBhcnQgb2YgdGhpcyBzdGFjaywgc28gaXQgd2lsbCBiZSByZW1vdmVkIHdoZW5cbiAgICogdGhlIHN0YWNrIGlzIGRlbGV0ZWQuXG4gICAqL1xuICByZXBvc2l0b3J5TmFtZTogc3RyaW5nO1xufVxuXG4vKipcbiAqIEFuIGludGVybmFsIGNsYXNzIHVzZWQgdG8gYWRvcHQgYW4gRUNSIHJlcG9zaXRvcnkgdXNlZCBmb3IgdGhlIGxvY2FsbHkgYnVpbHRcbiAqIGltYWdlIGludG8gdGhlIHN0YWNrLlxuICpcbiAqIFNpbmNlIHRoZSByZXBvc2l0b3J5IGlzIG5vdCBjcmVhdGVkIGJ5IHRoZSBzdGFjayAoYnV0IGJ5IHRoZSBDREsgdG9vbGtpdCksXG4gKiBhZG9wdGluZyB3aWxsIG1ha2UgdGhlIHJlcG9zaXRvcnkgXCJvd25lZFwiIGJ5IHRoZSBzdGFjay4gSXQgd2lsbCBiZSBjbGVhbmVkXG4gKiB1cCB3aGVuIHRoZSBzdGFjayBnZXRzIGRlbGV0ZWQsIHRvIGF2b2lkIGxlYXZpbmcgb3JwaGFuZWQgcmVwb3NpdG9yaWVzIG9uXG4gKiBzdGFjayBjbGVhbnVwLlxuICovXG5leHBvcnQgY2xhc3MgQWRvcHRlZFJlcG9zaXRvcnkgZXh0ZW5kcyBlY3IuUmVwb3NpdG9yeUJhc2Uge1xuICBwdWJsaWMgcmVhZG9ubHkgcmVwb3NpdG9yeU5hbWU6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IHJlcG9zaXRvcnlBcm46IHN0cmluZztcblxuICBwcml2YXRlIHJlYWRvbmx5IHBvbGljeURvY3VtZW50ID0gbmV3IGlhbS5Qb2xpY3lEb2N1bWVudCgpO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBjZGsuQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcml2YXRlIHJlYWRvbmx5IHByb3BzOiBBZG9wdGVkUmVwb3NpdG9yeVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIGNvbnN0IGZuID0gbmV3IGxhbWJkYS5TaW5nbGV0b25GdW5jdGlvbih0aGlzLCAnRnVuY3Rpb24nLCB7XG4gICAgICBydW50aW1lOiBsYW1iZGEuUnVudGltZS5Ob2RlanM4MTAsXG4gICAgICBsYW1iZGFQdXJwb3NlOiAnQWRvcHRFY3JSZXBvc2l0b3J5JyxcbiAgICAgIGhhbmRsZXI6ICdoYW5kbGVyLmhhbmRsZXInLFxuICAgICAgY29kZTogbGFtYmRhLkNvZGUuYXNzZXQocGF0aC5qb2luKF9fZGlybmFtZSwgJ2Fkb3B0LXJlcG9zaXRvcnknKSksXG4gICAgICB1dWlkOiAnZGJjNjBkZWYtYzU5NS00NGJjLWFhNWMtMjhjOTVkNjhmNjJjJyxcbiAgICAgIHRpbWVvdXQ6IDMwMFxuICAgIH0pO1xuXG4gICAgZm4uYWRkVG9Sb2xlUG9saWN5KG5ldyBpYW0uUG9saWN5U3RhdGVtZW50KClcbiAgICAgIC5hZGRSZXNvdXJjZShlY3IuUmVwb3NpdG9yeS5hcm5Gb3JMb2NhbFJlcG9zaXRvcnkocHJvcHMucmVwb3NpdG9yeU5hbWUsIHRoaXMpKVxuICAgICAgLmFkZEFjdGlvbnMoXG4gICAgICAgICdlY3I6R2V0UmVwb3NpdG9yeVBvbGljeScsXG4gICAgICAgICdlY3I6U2V0UmVwb3NpdG9yeVBvbGljeScsXG4gICAgICAgICdlY3I6RGVsZXRlUmVwb3NpdG9yeScsXG4gICAgICAgICdlY3I6TGlzdEltYWdlcycsXG4gICAgICAgICdlY3I6QmF0Y2hEZWxldGVJbWFnZSdcbiAgICAgICkpO1xuXG4gICAgY29uc3QgYWRvcHRlciA9IG5ldyBjZm4uQ3VzdG9tUmVzb3VyY2UodGhpcywgJ1Jlc291cmNlJywge1xuICAgICAgcmVzb3VyY2VUeXBlOiAnQ3VzdG9tOjpFQ1JBZG9wdGVkUmVwb3NpdG9yeScsXG4gICAgICBwcm92aWRlcjogY2ZuLkN1c3RvbVJlc291cmNlUHJvdmlkZXIubGFtYmRhKGZuKSxcbiAgICAgIHByb3BlcnRpZXM6IHtcbiAgICAgICAgUmVwb3NpdG9yeU5hbWU6IHByb3BzLnJlcG9zaXRvcnlOYW1lLFxuICAgICAgICBQb2xpY3lEb2N1bWVudDogdGhpcy5wb2xpY3lEb2N1bWVudFxuICAgICAgfVxuICAgIH0pO1xuICAgIGlmIChmbi5yb2xlKSB7XG4gICAgICAvLyBOZWVkIHRvIGV4cGxpY2l0bHkgZGVwZW5kIG9uIHRoZSByb2xlJ3MgcG9saWNpZXMsIHNvIHRoZXkgYXJlIGFwcGxpZWQgYmVmb3JlIHdlIHRyeSB0byB1c2UgdGhlbVxuICAgICAgYWRvcHRlci5ub2RlLmFkZERlcGVuZGVuY3koZm4ucm9sZSk7XG4gICAgfVxuXG4gICAgLy8gd2UgdXNlIHRoZSBGbjo6R2V0QXR0IHdpdGggdGhlIFJlcG9zaXRvcnlOYW1lIHJldHVybmVkIGJ5IHRoZSBjdXN0b21cbiAgICAvLyByZXNvdXJjZSBpbiBvcmRlciB0byBpbXBsaWNpdGx5IGNyZWF0ZSBhIGRlcGVuZGVuY3kgYmV0d2VlbiBjb25zdW1lcnNcbiAgICAvLyBhbmQgdGhlIGN1c3RvbSByZXNvdXJjZS5cbiAgICB0aGlzLnJlcG9zaXRvcnlOYW1lID0gYWRvcHRlci5nZXRBdHQoJ1JlcG9zaXRvcnlOYW1lJykudG9TdHJpbmcoKTtcblxuICAgIC8vIHRoaXMgdGhpcyByZXBvc2l0b3J5IGlzIFwibG9jYWxcIiB0byB0aGUgc3RhY2sgKGluIHRoZSBzYW1lIHJlZ2lvbi9hY2NvdW50KVxuICAgIC8vIHdlIGNhbiByZW5kZXIgaXQncyBBUk4gZnJvbSBpdCdzIG5hbWUuXG4gICAgdGhpcy5yZXBvc2l0b3J5QXJuID0gZWNyLlJlcG9zaXRvcnkuYXJuRm9yTG9jYWxSZXBvc2l0b3J5KHRoaXMucmVwb3NpdG9yeU5hbWUsIHRoaXMpO1xuICB9XG5cbiAgLyoqXG4gICAqIEV4cG9ydCB0aGlzIHJlcG9zaXRvcnkgZnJvbSB0aGUgc3RhY2tcbiAgICovXG4gIHB1YmxpYyBleHBvcnQoKSB7XG4gICAgcmV0dXJuIHRoaXMucHJvcHM7XG4gIH1cblxuICAvKipcbiAgICogQWRkcyBhIHN0YXRlbWVudCB0byB0aGUgcmVwb3NpdG9yeSByZXNvdXJjZSBwb2xpY3kuXG4gICAqXG4gICAqIENvbnRyYXJ5IHRvIG5vcm1hbCBpbXBvcnRlZCByZXBvc2l0b3JpZXMsIHdoaWNoIG5vLW9wIGhlcmUsIHdlIGNhblxuICAgKiB1c2UgdGhlIGN1c3RvbSByZXNvdXJjZSB0byBtb2RpZnkgdGhlIEVDUiByZXNvdXJjZSBwb2xpY3kgaWYgbmVlZGVkLlxuICAgKi9cbiAgcHVibGljIGFkZFRvUmVzb3VyY2VQb2xpY3koc3RhdGVtZW50OiBpYW0uUG9saWN5U3RhdGVtZW50KSB7XG4gICAgdGhpcy5wb2xpY3lEb2N1bWVudC5hZGRTdGF0ZW1lbnQoc3RhdGVtZW50KTtcbiAgfVxufVxuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/lib/image-asset.d.ts b/packages/@aws-cdk/assets-docker/lib/image-asset.d.ts new file mode 100644 index 0000000000000..635b26ea59550 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/image-asset.d.ts @@ -0,0 +1,50 @@ +import assets = require('@aws-cdk/assets'); +import ecr = require('@aws-cdk/aws-ecr'); +import cdk = require('@aws-cdk/cdk'); +export interface DockerImageAssetProps extends assets.CopyOptions { + /** + * The directory where the Dockerfile is stored + */ + readonly directory: string; + /** + * ECR repository name + * + * Specify this property if you need to statically address the image, e.g. + * from a Kubernetes Pod. Note, this is only the repository name, without the + * registry and the tag parts. + * + * @default automatically derived from the asset's ID. + */ + readonly repositoryName?: string; + /** + * Build args to pass to the `docker build` command + * + * @default no build args are passed + */ + readonly buildArgs?: { + [key: string]: string; + }; +} +/** + * An asset that represents a Docker image. + * + * The image will be created in build time and uploaded to an ECR repository. + */ +export declare class DockerImageAsset extends cdk.Construct implements assets.IAsset { + /** + * The full URI of the image (including a tag). Use this reference to pull + * the asset. + */ + imageUri: string; + /** + * Repository where the image is stored + */ + repository: ecr.IRepository; + readonly sourceHash: string; + readonly artifactHash: string; + /** + * Directory where the source files are stored + */ + private readonly directory; + constructor(scope: cdk.Construct, id: string, props: DockerImageAssetProps); +} diff --git a/packages/@aws-cdk/assets-docker/lib/image-asset.js b/packages/@aws-cdk/assets-docker/lib/image-asset.js new file mode 100644 index 0000000000000..a54eb9e6d2c8d --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/image-asset.js @@ -0,0 +1,62 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const assets = require("@aws-cdk/assets"); +const cdk = require("@aws-cdk/cdk"); +const cxapi = require("@aws-cdk/cx-api"); +const fs = require("fs"); +const path = require("path"); +const adopted_repository_1 = require("./adopted-repository"); +/** + * An asset that represents a Docker image. + * + * The image will be created in build time and uploaded to an ECR repository. + */ +class DockerImageAsset extends cdk.Construct { + constructor(scope, id, props) { + super(scope, id); + // resolve full path + const dir = path.resolve(props.directory); + if (!fs.existsSync(dir)) { + throw new Error(`Cannot find image directory at ${dir}`); + } + if (!fs.existsSync(path.join(dir, 'Dockerfile'))) { + throw new Error(`No 'Dockerfile' found in ${dir}`); + } + const staging = new assets.Staging(this, 'Staging', { + ...props, + sourcePath: dir + }); + this.directory = staging.stagedPath; + this.sourceHash = staging.sourceHash; + const imageNameParameter = new cdk.CfnParameter(this, 'ImageName', { + type: 'String', + description: `ECR repository name and tag asset "${this.node.path}"`, + }); + const asset = { + id: this.node.uniqueId, + packaging: 'container-image', + path: this.directory, + sourceHash: this.sourceHash, + imageNameParameter: imageNameParameter.logicalId, + repositoryName: props.repositoryName, + buildArgs: props.buildArgs + }; + this.node.addMetadata(cxapi.ASSET_METADATA, asset); + // Parse repository name and tag from the parameter (@sha256:) + // Example: cdk/cdkexampleimageb2d7f504@sha256:72c4f956379a43b5623d529ddd969f6826dde944d6221f445ff3e7add9875500 + const components = cdk.Fn.split('@sha256:', imageNameParameter.valueAsString); + const repositoryName = cdk.Fn.select(0, components).toString(); + const imageSha = cdk.Fn.select(1, components).toString(); + // Require that repository adoption happens first, so we route the + // input ARN into the Custom Resource and then get the URI which we use to + // refer to the image FROM the Custom Resource. + // + // If adoption fails (because the repository might be twice-adopted), we + // haven't already started using the image. + this.repository = new adopted_repository_1.AdoptedRepository(this, 'AdoptRepository', { repositoryName }); + this.imageUri = `${this.repository.repositoryUri}@sha256:${imageSha}`; + this.artifactHash = imageSha; + } +} +exports.DockerImageAsset = DockerImageAsset; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2UtYXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbWFnZS1hc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDBDQUEyQztBQUUzQyxvQ0FBcUM7QUFDckMseUNBQTBDO0FBQzFDLHlCQUEwQjtBQUMxQiw2QkFBOEI7QUFDOUIsNkRBQXlEO0FBMkJ6RDs7OztHQUlHO0FBQ0gsTUFBYSxnQkFBaUIsU0FBUSxHQUFHLENBQUMsU0FBUztJQW9CakQsWUFBWSxLQUFvQixFQUFFLEVBQVUsRUFBRSxLQUE0QjtRQUN4RSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRWpCLG9CQUFvQjtRQUNwQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMxQyxJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRTtZQUN2QixNQUFNLElBQUksS0FBSyxDQUFDLGtDQUFrQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1NBQzFEO1FBQ0QsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUMsRUFBRTtZQUNoRCxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixHQUFHLEVBQUUsQ0FBQyxDQUFDO1NBQ3BEO1FBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxTQUFTLEVBQUU7WUFDbEQsR0FBRyxLQUFLO1lBQ1IsVUFBVSxFQUFFLEdBQUc7U0FDaEIsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDO1FBQ3BDLElBQUksQ0FBQyxVQUFVLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQztRQUVyQyxNQUFNLGtCQUFrQixHQUFHLElBQUksR0FBRyxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFO1lBQ2pFLElBQUksRUFBRSxRQUFRO1lBQ2QsV0FBVyxFQUFFLHNDQUFzQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRztTQUNyRSxDQUFDLENBQUM7UUFFSCxNQUFNLEtBQUssR0FBMkM7WUFDcEQsRUFBRSxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUTtZQUN0QixTQUFTLEVBQUUsaUJBQWlCO1lBQzVCLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUztZQUNwQixVQUFVLEVBQUUsSUFBSSxDQUFDLFVBQVU7WUFDM0Isa0JBQWtCLEVBQUUsa0JBQWtCLENBQUMsU0FBUztZQUNoRCxjQUFjLEVBQUUsS0FBSyxDQUFDLGNBQWM7WUFDcEMsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1NBQzNCLENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsY0FBYyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBRW5ELDhFQUE4RTtRQUM5RSwrR0FBK0c7UUFDL0csTUFBTSxVQUFVLEdBQUcsR0FBRyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLGtCQUFrQixDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzlFLE1BQU0sY0FBYyxHQUFHLEdBQUcsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUMvRCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsVUFBVSxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUM7UUFFekQsa0VBQWtFO1FBQ2xFLDBFQUEwRTtRQUMxRSwrQ0FBK0M7UUFDL0MsRUFBRTtRQUNGLHdFQUF3RTtRQUN4RSwyQ0FBMkM7UUFDM0MsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLHNDQUFpQixDQUFDLElBQUksRUFBRSxpQkFBaUIsRUFBRSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7UUFDckYsSUFBSSxDQUFDLFFBQVEsR0FBRyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxXQUFXLFFBQVEsRUFBRSxDQUFDO1FBQ3RFLElBQUksQ0FBQyxZQUFZLEdBQUcsUUFBUSxDQUFDO0lBQy9CLENBQUM7Q0FDRjtBQXpFRCw0Q0F5RUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgYXNzZXRzID0gcmVxdWlyZSgnQGF3cy1jZGsvYXNzZXRzJyk7XG5pbXBvcnQgZWNyID0gcmVxdWlyZSgnQGF3cy1jZGsvYXdzLWVjcicpO1xuaW1wb3J0IGNkayA9IHJlcXVpcmUoJ0Bhd3MtY2RrL2NkaycpO1xuaW1wb3J0IGN4YXBpID0gcmVxdWlyZSgnQGF3cy1jZGsvY3gtYXBpJyk7XG5pbXBvcnQgZnMgPSByZXF1aXJlKCdmcycpO1xuaW1wb3J0IHBhdGggPSByZXF1aXJlKCdwYXRoJyk7XG5pbXBvcnQgeyBBZG9wdGVkUmVwb3NpdG9yeSB9IGZyb20gJy4vYWRvcHRlZC1yZXBvc2l0b3J5JztcblxuZXhwb3J0IGludGVyZmFjZSBEb2NrZXJJbWFnZUFzc2V0UHJvcHMgZXh0ZW5kcyBhc3NldHMuQ29weU9wdGlvbnMge1xuICAvKipcbiAgICogVGhlIGRpcmVjdG9yeSB3aGVyZSB0aGUgRG9ja2VyZmlsZSBpcyBzdG9yZWRcbiAgICovXG4gIHJlYWRvbmx5IGRpcmVjdG9yeTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBFQ1IgcmVwb3NpdG9yeSBuYW1lXG4gICAqXG4gICAqIFNwZWNpZnkgdGhpcyBwcm9wZXJ0eSBpZiB5b3UgbmVlZCB0byBzdGF0aWNhbGx5IGFkZHJlc3MgdGhlIGltYWdlLCBlLmcuXG4gICAqIGZyb20gYSBLdWJlcm5ldGVzIFBvZC4gTm90ZSwgdGhpcyBpcyBvbmx5IHRoZSByZXBvc2l0b3J5IG5hbWUsIHdpdGhvdXQgdGhlXG4gICAqIHJlZ2lzdHJ5IGFuZCB0aGUgdGFnIHBhcnRzLlxuICAgKlxuICAgKiBAZGVmYXVsdCBhdXRvbWF0aWNhbGx5IGRlcml2ZWQgZnJvbSB0aGUgYXNzZXQncyBJRC5cbiAgICovXG4gIHJlYWRvbmx5IHJlcG9zaXRvcnlOYW1lPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBCdWlsZCBhcmdzIHRvIHBhc3MgdG8gdGhlIGBkb2NrZXIgYnVpbGRgIGNvbW1hbmRcbiAgICpcbiAgICogQGRlZmF1bHQgbm8gYnVpbGQgYXJncyBhcmUgcGFzc2VkXG4gICAqL1xuICByZWFkb25seSBidWlsZEFyZ3M/OiB7IFtrZXk6IHN0cmluZ106IHN0cmluZyB9O1xufVxuXG4vKipcbiAqIEFuIGFzc2V0IHRoYXQgcmVwcmVzZW50cyBhIERvY2tlciBpbWFnZS5cbiAqXG4gKiBUaGUgaW1hZ2Ugd2lsbCBiZSBjcmVhdGVkIGluIGJ1aWxkIHRpbWUgYW5kIHVwbG9hZGVkIHRvIGFuIEVDUiByZXBvc2l0b3J5LlxuICovXG5leHBvcnQgY2xhc3MgRG9ja2VySW1hZ2VBc3NldCBleHRlbmRzIGNkay5Db25zdHJ1Y3QgaW1wbGVtZW50cyBhc3NldHMuSUFzc2V0IHtcbiAgLyoqXG4gICAqIFRoZSBmdWxsIFVSSSBvZiB0aGUgaW1hZ2UgKGluY2x1ZGluZyBhIHRhZykuIFVzZSB0aGlzIHJlZmVyZW5jZSB0byBwdWxsXG4gICAqIHRoZSBhc3NldC5cbiAgICovXG4gIHB1YmxpYyBpbWFnZVVyaTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBSZXBvc2l0b3J5IHdoZXJlIHRoZSBpbWFnZSBpcyBzdG9yZWRcbiAgICovXG4gIHB1YmxpYyByZXBvc2l0b3J5OiBlY3IuSVJlcG9zaXRvcnk7XG5cbiAgcHVibGljIHJlYWRvbmx5IHNvdXJjZUhhc2g6IHN0cmluZztcbiAgcHVibGljIHJlYWRvbmx5IGFydGlmYWN0SGFzaDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBEaXJlY3Rvcnkgd2hlcmUgdGhlIHNvdXJjZSBmaWxlcyBhcmUgc3RvcmVkXG4gICAqL1xuICBwcml2YXRlIHJlYWRvbmx5IGRpcmVjdG9yeTogc3RyaW5nO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBjZGsuQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogRG9ja2VySW1hZ2VBc3NldFByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIC8vIHJlc29sdmUgZnVsbCBwYXRoXG4gICAgY29uc3QgZGlyID0gcGF0aC5yZXNvbHZlKHByb3BzLmRpcmVjdG9yeSk7XG4gICAgaWYgKCFmcy5leGlzdHNTeW5jKGRpcikpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQ2Fubm90IGZpbmQgaW1hZ2UgZGlyZWN0b3J5IGF0ICR7ZGlyfWApO1xuICAgIH1cbiAgICBpZiAoIWZzLmV4aXN0c1N5bmMocGF0aC5qb2luKGRpciwgJ0RvY2tlcmZpbGUnKSkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgTm8gJ0RvY2tlcmZpbGUnIGZvdW5kIGluICR7ZGlyfWApO1xuICAgIH1cblxuICAgIGNvbnN0IHN0YWdpbmcgPSBuZXcgYXNzZXRzLlN0YWdpbmcodGhpcywgJ1N0YWdpbmcnLCB7XG4gICAgICAuLi5wcm9wcyxcbiAgICAgIHNvdXJjZVBhdGg6IGRpclxuICAgIH0pO1xuXG4gICAgdGhpcy5kaXJlY3RvcnkgPSBzdGFnaW5nLnN0YWdlZFBhdGg7XG4gICAgdGhpcy5zb3VyY2VIYXNoID0gc3RhZ2luZy5zb3VyY2VIYXNoO1xuXG4gICAgY29uc3QgaW1hZ2VOYW1lUGFyYW1ldGVyID0gbmV3IGNkay5DZm5QYXJhbWV0ZXIodGhpcywgJ0ltYWdlTmFtZScsIHtcbiAgICAgIHR5cGU6ICdTdHJpbmcnLFxuICAgICAgZGVzY3JpcHRpb246IGBFQ1IgcmVwb3NpdG9yeSBuYW1lIGFuZCB0YWcgYXNzZXQgXCIke3RoaXMubm9kZS5wYXRofVwiYCxcbiAgICB9KTtcblxuICAgIGNvbnN0IGFzc2V0OiBjeGFwaS5Db250YWluZXJJbWFnZUFzc2V0TWV0YWRhdGFFbnRyeSA9IHtcbiAgICAgIGlkOiB0aGlzLm5vZGUudW5pcXVlSWQsXG4gICAgICBwYWNrYWdpbmc6ICdjb250YWluZXItaW1hZ2UnLFxuICAgICAgcGF0aDogdGhpcy5kaXJlY3RvcnksXG4gICAgICBzb3VyY2VIYXNoOiB0aGlzLnNvdXJjZUhhc2gsXG4gICAgICBpbWFnZU5hbWVQYXJhbWV0ZXI6IGltYWdlTmFtZVBhcmFtZXRlci5sb2dpY2FsSWQsXG4gICAgICByZXBvc2l0b3J5TmFtZTogcHJvcHMucmVwb3NpdG9yeU5hbWUsXG4gICAgICBidWlsZEFyZ3M6IHByb3BzLmJ1aWxkQXJnc1xuICAgIH07XG5cbiAgICB0aGlzLm5vZGUuYWRkTWV0YWRhdGEoY3hhcGkuQVNTRVRfTUVUQURBVEEsIGFzc2V0KTtcblxuICAgIC8vIFBhcnNlIHJlcG9zaXRvcnkgbmFtZSBhbmQgdGFnIGZyb20gdGhlIHBhcmFtZXRlciAoPFJFUE9fTkFNRT5Ac2hhMjU2OjxUQUc+KVxuICAgIC8vIEV4YW1wbGU6IGNkay9jZGtleGFtcGxlaW1hZ2ViMmQ3ZjUwNEBzaGEyNTY6NzJjNGY5NTYzNzlhNDNiNTYyM2Q1MjlkZGQ5NjlmNjgyNmRkZTk0NGQ2MjIxZjQ0NWZmM2U3YWRkOTg3NTUwMFxuICAgIGNvbnN0IGNvbXBvbmVudHMgPSBjZGsuRm4uc3BsaXQoJ0BzaGEyNTY6JywgaW1hZ2VOYW1lUGFyYW1ldGVyLnZhbHVlQXNTdHJpbmcpO1xuICAgIGNvbnN0IHJlcG9zaXRvcnlOYW1lID0gY2RrLkZuLnNlbGVjdCgwLCBjb21wb25lbnRzKS50b1N0cmluZygpO1xuICAgIGNvbnN0IGltYWdlU2hhID0gY2RrLkZuLnNlbGVjdCgxLCBjb21wb25lbnRzKS50b1N0cmluZygpO1xuXG4gICAgLy8gUmVxdWlyZSB0aGF0IHJlcG9zaXRvcnkgYWRvcHRpb24gaGFwcGVucyBmaXJzdCwgc28gd2Ugcm91dGUgdGhlXG4gICAgLy8gaW5wdXQgQVJOIGludG8gdGhlIEN1c3RvbSBSZXNvdXJjZSBhbmQgdGhlbiBnZXQgdGhlIFVSSSB3aGljaCB3ZSB1c2UgdG9cbiAgICAvLyByZWZlciB0byB0aGUgaW1hZ2UgRlJPTSB0aGUgQ3VzdG9tIFJlc291cmNlLlxuICAgIC8vXG4gICAgLy8gSWYgYWRvcHRpb24gZmFpbHMgKGJlY2F1c2UgdGhlIHJlcG9zaXRvcnkgbWlnaHQgYmUgdHdpY2UtYWRvcHRlZCksIHdlXG4gICAgLy8gaGF2ZW4ndCBhbHJlYWR5IHN0YXJ0ZWQgdXNpbmcgdGhlIGltYWdlLlxuICAgIHRoaXMucmVwb3NpdG9yeSA9IG5ldyBBZG9wdGVkUmVwb3NpdG9yeSh0aGlzLCAnQWRvcHRSZXBvc2l0b3J5JywgeyByZXBvc2l0b3J5TmFtZSB9KTtcbiAgICB0aGlzLmltYWdlVXJpID0gYCR7dGhpcy5yZXBvc2l0b3J5LnJlcG9zaXRvcnlVcml9QHNoYTI1Njoke2ltYWdlU2hhfWA7XG4gICAgdGhpcy5hcnRpZmFjdEhhc2ggPSBpbWFnZVNoYTtcbiAgfVxufVxuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/lib/index.d.ts b/packages/@aws-cdk/assets-docker/lib/index.d.ts new file mode 100644 index 0000000000000..579fee533587d --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/index.d.ts @@ -0,0 +1 @@ +export * from './image-asset'; diff --git a/packages/@aws-cdk/assets-docker/lib/index.js b/packages/@aws-cdk/assets-docker/lib/index.js new file mode 100644 index 0000000000000..837d8c8592eb4 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/lib/index.js @@ -0,0 +1,7 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./image-asset")); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLG1DQUE4QiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCAqIGZyb20gJy4vaW1hZ2UtYXNzZXQnO1xuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/lib/index.ts b/packages/@aws-cdk/assets-docker/lib/index.ts deleted file mode 100644 index b03033c94d008..0000000000000 --- a/packages/@aws-cdk/assets-docker/lib/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// tslint:disable-next-line:no-console -console.error('the @aws-cdk/assets-docker is deprecated. use @aws-cdk/aws-ecr-assets'); diff --git a/packages/@aws-cdk/assets-docker/package-lock.json b/packages/@aws-cdk/assets-docker/package-lock.json deleted file mode 100644 index 6aff698c6ea31..0000000000000 --- a/packages/@aws-cdk/assets-docker/package-lock.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "@aws-cdk/assets-docker", - "version": "0.34.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/proxyquire": { - "version": "1.3.28", - "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.28.tgz", - "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", - "dev": true - }, - "fill-keys": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "proxyquire": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.0.tgz", - "integrity": "sha512-kptdFArCfGRtQFv3Qwjr10lwbEV0TBJYvfqzhwucyfEXqVgmnAkyEw/S3FYzR5HI9i5QOq4rcqQjZ6AlknlCDQ==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.0", - "resolve": "~1.8.1" - } - }, - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - } - } -} diff --git a/packages/@aws-cdk/assets-docker/package.json b/packages/@aws-cdk/assets-docker/package.json deleted file mode 100644 index 9dedb2f19cef1..0000000000000 --- a/packages/@aws-cdk/assets-docker/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "@aws-cdk/assets-docker", - "version": "0.35.0", - "deprecated": "moved to @aws-cdk/aws-ecr-assets", - "description": "deprecated: moved to @aws-cdk/aws-ecr-assets", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.awscdk.assets.docker", - "maven": { - "groupId": "software.amazon.awscdk", - "artifactId": "cdk-assets-docker" - } - }, - "dotnet": { - "namespace": "Amazon.CDK.Assets.Docker", - "packageId": "Amazon.CDK.Assets.Docker", - "signAssembly": true, - "assemblyOriginatorKeyFile": "../../key.snk" - }, - "python": { - "distName": "aws-cdk.assets-docker", - "module": "aws_cdk.assets_docker" - } - } - }, - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git", - "directory": "packages/@aws-cdk/assets-docker" - }, - "scripts": { - "build": "cdk-build", - "watch": "cdk-watch", - "lint": "cdk-lint", - "test": "cdk-test", - "integ": "cdk-integ", - "pkglint": "pkglint -f", - "package": "cdk-package", - "awslint": "cdk-awslint", - "build+test+package": "npm run build+test && npm run package", - "build+test": "npm run build && npm test" - }, - "keywords": [ - "aws", - "cdk", - "constructs", - "assets", - "docker" - ], - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "devDependencies": { - "cdk-build-tools": "^0.35.0", - "cdk-integ-tools": "^0.35.0", - "pkglint": "^0.35.0" - }, - "homepage": "https://github.com/awslabs/aws-cdk", - "engines": { - "node": ">= 8.10.0" - }, - "stability": "deprecated" -} diff --git a/packages/@aws-cdk/assets-docker/test/integ.assets-docker.d.ts b/packages/@aws-cdk/assets-docker/test/integ.assets-docker.d.ts new file mode 100644 index 0000000000000..cb0ff5c3b541f --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/integ.assets-docker.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/@aws-cdk/assets-docker/test/integ.assets-docker.js b/packages/@aws-cdk/assets-docker/test/integ.assets-docker.js new file mode 100644 index 0000000000000..349f11ff43a82 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/integ.assets-docker.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const cdk = require("@aws-cdk/cdk"); +const path = require("path"); +const assets = require("../lib"); +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-assets-docker'); +const asset = new assets.DockerImageAsset(stack, 'DockerImage', { + directory: path.join(__dirname, 'demo-image'), +}); +new cdk.CfnOutput(stack, 'ArtifactHash', { value: asset.artifactHash }); +new cdk.CfnOutput(stack, 'ImageUri', { value: asset.imageUri }); +app.synth(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZWcuYXNzZXRzLWRvY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImludGVnLmFzc2V0cy1kb2NrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxvQ0FBcUM7QUFDckMsNkJBQThCO0FBQzlCLGlDQUFrQztBQUVsQyxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQztBQUMxQixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFLHFCQUFxQixDQUFDLENBQUM7QUFFeEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxNQUFNLENBQUMsZ0JBQWdCLENBQUMsS0FBSyxFQUFFLGFBQWEsRUFBRTtJQUM5RCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDO0NBQzlDLENBQUMsQ0FBQztBQUVILElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsY0FBYyxFQUFFLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxZQUFZLEVBQUUsQ0FBQyxDQUFDO0FBQ3hFLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsVUFBVSxFQUFFLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0FBRWhFLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjZGsgPSByZXF1aXJlKCdAYXdzLWNkay9jZGsnKTtcbmltcG9ydCBwYXRoID0gcmVxdWlyZSgncGF0aCcpO1xuaW1wb3J0IGFzc2V0cyA9IHJlcXVpcmUoJy4uL2xpYicpO1xuXG5jb25zdCBhcHAgPSBuZXcgY2RrLkFwcCgpO1xuY29uc3Qgc3RhY2sgPSBuZXcgY2RrLlN0YWNrKGFwcCwgJ2ludGVnLWFzc2V0cy1kb2NrZXInKTtcblxuY29uc3QgYXNzZXQgPSBuZXcgYXNzZXRzLkRvY2tlckltYWdlQXNzZXQoc3RhY2ssICdEb2NrZXJJbWFnZScsIHtcbiAgZGlyZWN0b3J5OiBwYXRoLmpvaW4oX19kaXJuYW1lLCAnZGVtby1pbWFnZScpLFxufSk7XG5cbm5ldyBjZGsuQ2ZuT3V0cHV0KHN0YWNrLCAnQXJ0aWZhY3RIYXNoJywgeyB2YWx1ZTogYXNzZXQuYXJ0aWZhY3RIYXNoIH0pO1xubmV3IGNkay5DZm5PdXRwdXQoc3RhY2ssICdJbWFnZVVyaScsIHsgdmFsdWU6IGFzc2V0LmltYWdlVXJpIH0pO1xuXG5hcHAuc3ludGgoKTtcbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/test/test.adpot-repo.d.ts b/packages/@aws-cdk/assets-docker/test/test.adpot-repo.d.ts new file mode 100644 index 0000000000000..5cde3dfde89e1 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/test.adpot-repo.d.ts @@ -0,0 +1,11 @@ +import { Test } from 'nodeunit'; +declare const _default: { + 'exercise handler create with policy'(test: Test): Promise; + 'exercise handler create with policy with object statement'(test: Test): Promise; + 'exercise handler create with policy with array statement'(test: Test): Promise; + 'exercise handler create'(test: Test): Promise; + 'exercise handler delete'(test: Test): Promise; + 'exercise "delete" handler when repository doesnt exist'(test: Test): Promise; + 'exercise "create" handler when repository doesnt exist'(test: Test): Promise; +}; +export = _default; diff --git a/packages/@aws-cdk/assets-docker/test/test.adpot-repo.js b/packages/@aws-cdk/assets-docker/test/test.adpot-repo.js new file mode 100644 index 0000000000000..2f9f248970031 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/test.adpot-repo.js @@ -0,0 +1,329 @@ +"use strict"; +const path = require("path"); +const proxyquire = require("proxyquire"); +let ecrMock; +function ECRWithEmptyPolicy() { + ecrMock = new ECR({ asdf: 'asdf' }); + return ecrMock; +} +function ECRWithOwningPolicy() { + return new ECR({ + Statement: [ + { + Sid: 'StackId', + Effect: "Deny", + Action: "OwnedBy:CDKStack", + Principal: "*" + } + ] + }); +} +function ECRWithRepositoryNotFound() { + const ecr = new ECR({}); + ecr.shouldThrowNotFound = true; + return ecr; +} +class ECR { + constructor(policy) { + this.policy = policy; + this.shouldThrowNotFound = false; + } + getRepositoryPolicy() { + const self = this; + return { + async promise() { + if (self.shouldThrowNotFound) { + return self.throwNotFound(); + } + return { policyText: JSON.stringify(self.policy) }; + } + }; + } + setRepositoryPolicy(req) { + const self = this; + this.lastSetRepositoryPolicyRequest = req; + return { + async promise() { + if (self.shouldThrowNotFound) { + return self.throwNotFound(); + } + return; + } + }; + } + listImages() { + return { + async promise() { + return { imageIds: [] }; + } + }; + } + batchDeleteImage() { + const self = this; + return { + async promise() { + if (self.shouldThrowNotFound) { + return self.throwNotFound(); + } + return {}; + } + }; + } + deleteRepository() { + const self = this; + return { + async promise() { + if (self.shouldThrowNotFound) { + return self.throwNotFound(); + } + return {}; + } + }; + } + throwNotFound() { + const err = new Error('Simulated RepositoryPolicyNotFoundException'); + err.code = 'RepositoryPolicyNotFoundException'; + throw err; + } +} +module.exports = { + async 'exercise handler create with policy'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { + '@noCallThru': true, + "ECR": ECRWithEmptyPolicy, + } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + PolicyDocument: { + Version: '2008-10-01', + My: 'Document' + } + }, + RequestType: 'Create', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { + My: "Document", + Version: '2008-10-01', + Statement: [ + { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } + ] + }); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise handler create with policy with object statement'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { + '@noCallThru': true, + "ECR": ECRWithEmptyPolicy, + } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + PolicyDocument: { + Statement: { Action: 'boom' } + } + }, + RequestType: 'Create', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { + Version: '2008-10-17', + Statement: [ + { Action: 'boom' }, + { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } + ] + }); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise handler create with policy with array statement'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { + '@noCallThru': true, + "ECR": ECRWithEmptyPolicy, + } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + PolicyDocument: { + Statement: [{ Action: 'boom' }, { Resource: "foo" }] + } + }, + RequestType: 'Create', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(JSON.parse(ecrMock.lastSetRepositoryPolicyRequest.policyText), { + Version: '2008-10-17', + Statement: [ + { Action: "boom" }, + { Resource: "foo" }, + { Sid: "StackId", Effect: "Deny", Action: "OwnedBy:CDKStack", Principal: "*" } + ] + }); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise handler create'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { + '@noCallThru': true, + "ECR": ECRWithEmptyPolicy, + } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + }, + RequestType: 'Create', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise handler delete'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithOwningPolicy } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + }, + RequestType: 'Delete', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise "delete" handler when repository doesnt exist'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithRepositoryNotFound } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + }, + RequestType: 'Delete', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(output, { + responseStatus: 'SUCCESS', + reason: 'OK', + physId: 'RepositoryName', + data: { + RepositoryName: 'RepositoryName' + } + }); + test.done(); + }, + async 'exercise "create" handler when repository doesnt exist'(test) { + const handler = proxyquire(path.resolve(__dirname, '..', 'lib', 'adopt-repository', 'handler'), { + 'aws-sdk': { '@noCallThru': true, "ECR": ECRWithRepositoryNotFound } + }); + let output; + async function response(responseStatus, reason, physId, data) { + output = { responseStatus, reason, physId, data }; + } + await handler.handler({ + StackId: 'StackId', + ResourceProperties: { + RepositoryName: 'RepositoryName', + }, + RequestType: 'Create', + ResponseURL: 'https://localhost/test' + }, { + logStreamName: 'xyz', + }, undefined, response); + test.deepEqual(output, { + responseStatus: 'FAILED', + reason: 'Simulated RepositoryPolicyNotFoundException', + physId: 'xyz', + data: {} + }); + test.done(); + }, +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5hZHBvdC1yZXBvLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidGVzdC5hZHBvdC1yZXBvLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSw2QkFBOEI7QUFDOUIseUNBQTBDO0FBRTFDLElBQUksT0FBWSxDQUFDO0FBd1JqQixTQUFTLGtCQUFrQjtJQUN6QixPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUNwQyxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQsU0FBUyxtQkFBbUI7SUFDMUIsT0FBTyxJQUFJLEdBQUcsQ0FBQztRQUNiLFNBQVMsRUFBRTtZQUNUO2dCQUNFLEdBQUcsRUFBRSxTQUFTO2dCQUNkLE1BQU0sRUFBRSxNQUFNO2dCQUNkLE1BQU0sRUFBRSxrQkFBa0I7Z0JBQzFCLFNBQVMsRUFBRSxHQUFHO2FBQ2Y7U0FDRjtLQUNGLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxTQUFTLHlCQUF5QjtJQUNoQyxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUN4QixHQUFHLENBQUMsbUJBQW1CLEdBQUcsSUFBSSxDQUFDO0lBQy9CLE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVELE1BQU0sR0FBRztJQUlQLFlBQTJCLE1BQVc7UUFBWCxXQUFNLEdBQU4sTUFBTSxDQUFLO1FBRi9CLHdCQUFtQixHQUFHLEtBQUssQ0FBQztJQUduQyxDQUFDO0lBRU0sbUJBQW1CO1FBQ3hCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQztRQUNsQixPQUFPO1lBQ0wsS0FBSyxDQUFDLE9BQU87Z0JBQ1gsSUFBSSxJQUFJLENBQUMsbUJBQW1CLEVBQUU7b0JBQUUsT0FBTyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7aUJBQUU7Z0JBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztZQUNyRCxDQUFDO1NBQ0YsQ0FBQztJQUNKLENBQUM7SUFFTSxtQkFBbUIsQ0FBQyxHQUFRO1FBQ2pDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQztRQUNsQixJQUFJLENBQUMsOEJBQThCLEdBQUcsR0FBRyxDQUFDO1FBRTFDLE9BQU87WUFDTCxLQUFLLENBQUMsT0FBTztnQkFDWCxJQUFJLElBQUksQ0FBQyxtQkFBbUIsRUFBRTtvQkFBRSxPQUFPLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztpQkFBRTtnQkFDOUQsT0FBTztZQUNULENBQUM7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUVNLFVBQVU7UUFDZixPQUFPO1lBQ0wsS0FBSyxDQUFDLE9BQU87Z0JBQ1gsT0FBTyxFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsQ0FBQztZQUMxQixDQUFDO1NBQ0YsQ0FBQztJQUNKLENBQUM7SUFFTSxnQkFBZ0I7UUFDckIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2xCLE9BQU87WUFDTCxLQUFLLENBQUMsT0FBTztnQkFDWCxJQUFJLElBQUksQ0FBQyxtQkFBbUIsRUFBRTtvQkFBRSxPQUFPLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztpQkFBRTtnQkFDOUQsT0FBTyxFQUFFLENBQUM7WUFDWixDQUFDO1NBQ0YsQ0FBQztJQUNKLENBQUM7SUFFTSxnQkFBZ0I7UUFDckIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2xCLE9BQU87WUFDTCxLQUFLLENBQUMsT0FBTztnQkFDWCxJQUFJLElBQUksQ0FBQyxtQkFBbUIsRUFBRTtvQkFBRSxPQUFPLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztpQkFBRTtnQkFDOUQsT0FBTyxFQUFFLENBQUM7WUFDWixDQUFDO1NBQ0YsQ0FBQztJQUNKLENBQUM7SUFFTyxhQUFhO1FBQ25CLE1BQU0sR0FBRyxHQUFHLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7UUFDcEUsR0FBVyxDQUFDLElBQUksR0FBRyxtQ0FBbUMsQ0FBQztRQUN4RCxNQUFNLEdBQUcsQ0FBQztJQUNaLENBQUM7Q0FDRjtBQTVXRCxpQkFBUztJQUNQLEtBQUssQ0FBQyxxQ0FBcUMsQ0FBQyxJQUFVO1FBQ3BELE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxFQUFFO1lBQzlGLFNBQVMsRUFBRTtnQkFDVCxhQUFhLEVBQUUsSUFBSTtnQkFDbkIsS0FBSyxFQUFFLGtCQUFrQjthQUMxQjtTQUNGLENBQUMsQ0FBQztRQUVILElBQUksTUFBTSxDQUFDO1FBQ1gsS0FBSyxVQUFVLFFBQVEsQ0FBQyxjQUFzQixFQUFFLE1BQWMsRUFBRSxNQUFjLEVBQUUsSUFBUztZQUN2RixNQUFNLEdBQUcsRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsQ0FBQztRQUNwRCxDQUFDO1FBRUQsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDO1lBQ3BCLE9BQU8sRUFBRSxTQUFTO1lBQ2xCLGtCQUFrQixFQUFFO2dCQUNsQixjQUFjLEVBQUUsZ0JBQWdCO2dCQUNoQyxjQUFjLEVBQUU7b0JBQ2QsT0FBTyxFQUFFLFlBQVk7b0JBQ3JCLEVBQUUsRUFBRSxVQUFVO2lCQUNmO2FBQ0Y7WUFDRCxXQUFXLEVBQUUsUUFBUTtZQUNyQixXQUFXLEVBQUUsd0JBQXdCO1NBQ3RDLEVBQUU7WUFDRCxhQUFhLEVBQUUsS0FBSztTQUNyQixFQUFFLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUV4QixJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLDhCQUE4QixDQUFDLFVBQVUsQ0FBQyxFQUFFO1lBQzVFLEVBQUUsRUFBRSxVQUFVO1lBQ2QsT0FBTyxFQUFFLFlBQVk7WUFDckIsU0FBUyxFQUFFO2dCQUNULEVBQUUsR0FBRyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFO2FBQy9FO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUU7WUFDckIsY0FBYyxFQUFFLFNBQVM7WUFDekIsTUFBTSxFQUFFLElBQUk7WUFDWixNQUFNLEVBQUUsZ0JBQWdCO1lBQ3hCLElBQUksRUFBRTtnQkFDSixjQUFjLEVBQUUsZ0JBQWdCO2FBQ2pDO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztJQUVELEtBQUssQ0FBQywyREFBMkQsQ0FBQyxJQUFVO1FBQzFFLE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxFQUFFO1lBQzlGLFNBQVMsRUFBRTtnQkFDVCxhQUFhLEVBQUUsSUFBSTtnQkFDbkIsS0FBSyxFQUFFLGtCQUFrQjthQUMxQjtTQUNGLENBQUMsQ0FBQztRQUVILElBQUksTUFBTSxDQUFDO1FBQ1gsS0FBSyxVQUFVLFFBQVEsQ0FBQyxjQUFzQixFQUFFLE1BQWMsRUFBRSxNQUFjLEVBQUUsSUFBUztZQUN2RixNQUFNLEdBQUcsRUFBRSxjQUFjLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsQ0FBQztRQUNwRCxDQUFDO1FBRUQsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDO1lBQ3BCLE9BQU8sRUFBRSxTQUFTO1lBQ2xCLGtCQUFrQixFQUFFO2dCQUNsQixjQUFjLEVBQUUsZ0JBQWdCO2dCQUNoQyxjQUFjLEVBQUU7b0JBQ2QsU0FBUyxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRTtpQkFDOUI7YUFDRjtZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsOEJBQThCLENBQUMsVUFBVSxDQUFDLEVBQUU7WUFDNUUsT0FBTyxFQUFFLFlBQVk7WUFDckIsU0FBUyxFQUFFO2dCQUNULEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRTtnQkFDbEIsRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLGtCQUFrQixFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUU7YUFDL0U7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRTtZQUNyQixjQUFjLEVBQUUsU0FBUztZQUN6QixNQUFNLEVBQUUsSUFBSTtZQUNaLE1BQU0sRUFBRSxnQkFBZ0I7WUFDeEIsSUFBSSxFQUFFO2dCQUNKLGNBQWMsRUFBRSxnQkFBZ0I7YUFDakM7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDZCxDQUFDO0lBRUQsS0FBSyxDQUFDLDBEQUEwRCxDQUFDLElBQVU7UUFDekUsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsU0FBUyxDQUFDLEVBQUU7WUFDOUYsU0FBUyxFQUFFO2dCQUNULGFBQWEsRUFBRSxJQUFJO2dCQUNuQixLQUFLLEVBQUUsa0JBQWtCO2FBQzFCO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxNQUFNLENBQUM7UUFDWCxLQUFLLFVBQVUsUUFBUSxDQUFDLGNBQXNCLEVBQUUsTUFBYyxFQUFFLE1BQWMsRUFBRSxJQUFTO1lBQ3ZGLE1BQU0sR0FBRyxFQUFFLGNBQWMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDO1FBQ3BELENBQUM7UUFFRCxNQUFNLE9BQU8sQ0FBQyxPQUFPLENBQUM7WUFDcEIsT0FBTyxFQUFFLFNBQVM7WUFDbEIsa0JBQWtCLEVBQUU7Z0JBQ2xCLGNBQWMsRUFBRSxnQkFBZ0I7Z0JBQ2hDLGNBQWMsRUFBRTtvQkFDZCxTQUFTLEVBQUUsQ0FBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsQ0FBRztpQkFDeEQ7YUFDRjtZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsOEJBQThCLENBQUMsVUFBVSxDQUFDLEVBQUU7WUFDNUUsT0FBTyxFQUFFLFlBQVk7WUFDckIsU0FBUyxFQUFFO2dCQUNULEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRTtnQkFDbEIsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFO2dCQUNuQixFQUFFLEdBQUcsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsa0JBQWtCLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRTthQUMvRTtTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3JCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLE1BQU0sRUFBRSxJQUFJO1lBQ1osTUFBTSxFQUFFLGdCQUFnQjtZQUN4QixJQUFJLEVBQUU7Z0JBQ0osY0FBYyxFQUFFLGdCQUFnQjthQUNqQztTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMseUJBQXlCLENBQUMsSUFBVTtRQUN4QyxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxTQUFTLENBQUMsRUFBRTtZQUM5RixTQUFTLEVBQUU7Z0JBQ1QsYUFBYSxFQUFFLElBQUk7Z0JBQ25CLEtBQUssRUFBRSxrQkFBa0I7YUFDMUI7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLE1BQU0sQ0FBQztRQUNYLEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLElBQVM7WUFDdkYsTUFBTSxHQUFHLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDcEQsQ0FBQztRQUVELE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQztZQUNwQixPQUFPLEVBQUUsU0FBUztZQUNsQixrQkFBa0IsRUFBRTtnQkFDbEIsY0FBYyxFQUFFLGdCQUFnQjthQUNqQztZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3JCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLE1BQU0sRUFBRSxJQUFJO1lBQ1osTUFBTSxFQUFFLGdCQUFnQjtZQUN4QixJQUFJLEVBQUU7Z0JBQ0osY0FBYyxFQUFFLGdCQUFnQjthQUNqQztTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMseUJBQXlCLENBQUMsSUFBVTtRQUN4QyxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxTQUFTLENBQUMsRUFBRTtZQUM5RixTQUFTLEVBQUUsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxtQkFBbUIsRUFBRTtTQUMvRCxDQUFDLENBQUM7UUFFSCxJQUFJLE1BQU0sQ0FBQztRQUNYLEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLElBQVM7WUFDdkYsTUFBTSxHQUFHLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDcEQsQ0FBQztRQUVELE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQztZQUNwQixPQUFPLEVBQUUsU0FBUztZQUNsQixrQkFBa0IsRUFBRTtnQkFDbEIsY0FBYyxFQUFFLGdCQUFnQjthQUNqQztZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3JCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLE1BQU0sRUFBRSxJQUFJO1lBQ1osTUFBTSxFQUFFLGdCQUFnQjtZQUN4QixJQUFJLEVBQUU7Z0JBQ0osY0FBYyxFQUFFLGdCQUFnQjthQUNqQztTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMsd0RBQXdELENBQUMsSUFBVTtRQUN2RSxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxTQUFTLENBQUMsRUFBRTtZQUM5RixTQUFTLEVBQUUsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSx5QkFBeUIsRUFBRTtTQUNyRSxDQUFDLENBQUM7UUFFSCxJQUFJLE1BQU0sQ0FBQztRQUNYLEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLElBQVM7WUFDdkYsTUFBTSxHQUFHLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDcEQsQ0FBQztRQUVELE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQztZQUNwQixPQUFPLEVBQUUsU0FBUztZQUNsQixrQkFBa0IsRUFBRTtnQkFDbEIsY0FBYyxFQUFFLGdCQUFnQjthQUNqQztZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3JCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLE1BQU0sRUFBRSxJQUFJO1lBQ1osTUFBTSxFQUFFLGdCQUFnQjtZQUN4QixJQUFJLEVBQUU7Z0JBQ0osY0FBYyxFQUFFLGdCQUFnQjthQUNqQztTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMsd0RBQXdELENBQUMsSUFBVTtRQUN2RSxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxTQUFTLENBQUMsRUFBRTtZQUM5RixTQUFTLEVBQUUsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSx5QkFBeUIsRUFBRTtTQUNyRSxDQUFDLENBQUM7UUFFSCxJQUFJLE1BQU0sQ0FBQztRQUNYLEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxNQUFjLEVBQUUsTUFBYyxFQUFFLElBQVM7WUFDdkYsTUFBTSxHQUFHLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDcEQsQ0FBQztRQUVELE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQztZQUNwQixPQUFPLEVBQUUsU0FBUztZQUNsQixrQkFBa0IsRUFBRTtnQkFDbEIsY0FBYyxFQUFFLGdCQUFnQjthQUNqQztZQUNELFdBQVcsRUFBRSxRQUFRO1lBQ3JCLFdBQVcsRUFBRSx3QkFBd0I7U0FDdEMsRUFBRTtZQUNELGFBQWEsRUFBRSxLQUFLO1NBQ3JCLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFO1lBQ3JCLGNBQWMsRUFBRSxRQUFRO1lBQ3hCLE1BQU0sRUFBRSw2Q0FBNkM7WUFDckQsTUFBTSxFQUFFLEtBQUs7WUFDYixJQUFJLEVBQUUsRUFBRztTQUNWLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7Q0FDRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVGVzdCB9IGZyb20gJ25vZGV1bml0JztcbmltcG9ydCBwYXRoID0gcmVxdWlyZSgncGF0aCcpO1xuaW1wb3J0IHByb3h5cXVpcmUgPSByZXF1aXJlKCdwcm94eXF1aXJlJyk7XG5cbmxldCBlY3JNb2NrOiBhbnk7XG5cbmV4cG9ydCA9IHtcbiAgYXN5bmMgJ2V4ZXJjaXNlIGhhbmRsZXIgY3JlYXRlIHdpdGggcG9saWN5Jyh0ZXN0OiBUZXN0KSB7XG4gICAgY29uc3QgaGFuZGxlciA9IHByb3h5cXVpcmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uJywgJ2xpYicsICdhZG9wdC1yZXBvc2l0b3J5JywgJ2hhbmRsZXInKSwge1xuICAgICAgJ2F3cy1zZGsnOiB7XG4gICAgICAgICdAbm9DYWxsVGhydSc6IHRydWUsXG4gICAgICAgIFwiRUNSXCI6IEVDUldpdGhFbXB0eVBvbGljeSxcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGxldCBvdXRwdXQ7XG4gICAgYXN5bmMgZnVuY3Rpb24gcmVzcG9uc2UocmVzcG9uc2VTdGF0dXM6IHN0cmluZywgcmVhc29uOiBzdHJpbmcsIHBoeXNJZDogc3RyaW5nLCBkYXRhOiBhbnkpIHtcbiAgICAgIG91dHB1dCA9IHsgcmVzcG9uc2VTdGF0dXMsIHJlYXNvbiwgcGh5c0lkLCBkYXRhIH07XG4gICAgfVxuXG4gICAgYXdhaXQgaGFuZGxlci5oYW5kbGVyKHtcbiAgICAgIFN0YWNrSWQ6ICdTdGFja0lkJyxcbiAgICAgIFJlc291cmNlUHJvcGVydGllczoge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgICAgUG9saWN5RG9jdW1lbnQ6IHtcbiAgICAgICAgICBWZXJzaW9uOiAnMjAwOC0xMC0wMScsXG4gICAgICAgICAgTXk6ICdEb2N1bWVudCdcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIFJlcXVlc3RUeXBlOiAnQ3JlYXRlJyxcbiAgICAgIFJlc3BvbnNlVVJMOiAnaHR0cHM6Ly9sb2NhbGhvc3QvdGVzdCdcbiAgICB9LCB7XG4gICAgICBsb2dTdHJlYW1OYW1lOiAneHl6JyxcbiAgICB9LCB1bmRlZmluZWQsIHJlc3BvbnNlKTtcblxuICAgIHRlc3QuZGVlcEVxdWFsKEpTT04ucGFyc2UoZWNyTW9jay5sYXN0U2V0UmVwb3NpdG9yeVBvbGljeVJlcXVlc3QucG9saWN5VGV4dCksIHtcbiAgICAgIE15OiBcIkRvY3VtZW50XCIsXG4gICAgICBWZXJzaW9uOiAnMjAwOC0xMC0wMScsXG4gICAgICBTdGF0ZW1lbnQ6IFtcbiAgICAgICAgeyBTaWQ6IFwiU3RhY2tJZFwiLCBFZmZlY3Q6IFwiRGVueVwiLCBBY3Rpb246IFwiT3duZWRCeTpDREtTdGFja1wiLCBQcmluY2lwYWw6IFwiKlwiIH1cbiAgICAgIF1cbiAgICB9KTtcblxuICAgIHRlc3QuZGVlcEVxdWFsKG91dHB1dCwge1xuICAgICAgcmVzcG9uc2VTdGF0dXM6ICdTVUNDRVNTJyxcbiAgICAgIHJlYXNvbjogJ09LJyxcbiAgICAgIHBoeXNJZDogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgIGRhdGE6IHtcbiAgICAgICAgUmVwb3NpdG9yeU5hbWU6ICdSZXBvc2l0b3J5TmFtZSdcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHRlc3QuZG9uZSgpO1xuICB9LFxuXG4gIGFzeW5jICdleGVyY2lzZSBoYW5kbGVyIGNyZWF0ZSB3aXRoIHBvbGljeSB3aXRoIG9iamVjdCBzdGF0ZW1lbnQnKHRlc3Q6IFRlc3QpIHtcbiAgICBjb25zdCBoYW5kbGVyID0gcHJveHlxdWlyZShwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi4nLCAnbGliJywgJ2Fkb3B0LXJlcG9zaXRvcnknLCAnaGFuZGxlcicpLCB7XG4gICAgICAnYXdzLXNkayc6IHtcbiAgICAgICAgJ0Bub0NhbGxUaHJ1JzogdHJ1ZSxcbiAgICAgICAgXCJFQ1JcIjogRUNSV2l0aEVtcHR5UG9saWN5LFxuICAgICAgfVxuICAgIH0pO1xuXG4gICAgbGV0IG91dHB1dDtcbiAgICBhc3luYyBmdW5jdGlvbiByZXNwb25zZShyZXNwb25zZVN0YXR1czogc3RyaW5nLCByZWFzb246IHN0cmluZywgcGh5c0lkOiBzdHJpbmcsIGRhdGE6IGFueSkge1xuICAgICAgb3V0cHV0ID0geyByZXNwb25zZVN0YXR1cywgcmVhc29uLCBwaHlzSWQsIGRhdGEgfTtcbiAgICB9XG5cbiAgICBhd2FpdCBoYW5kbGVyLmhhbmRsZXIoe1xuICAgICAgU3RhY2tJZDogJ1N0YWNrSWQnLFxuICAgICAgUmVzb3VyY2VQcm9wZXJ0aWVzOiB7XG4gICAgICAgIFJlcG9zaXRvcnlOYW1lOiAnUmVwb3NpdG9yeU5hbWUnLFxuICAgICAgICBQb2xpY3lEb2N1bWVudDoge1xuICAgICAgICAgIFN0YXRlbWVudDogeyBBY3Rpb246ICdib29tJyB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICBSZXF1ZXN0VHlwZTogJ0NyZWF0ZScsXG4gICAgICBSZXNwb25zZVVSTDogJ2h0dHBzOi8vbG9jYWxob3N0L3Rlc3QnXG4gICAgfSwge1xuICAgICAgbG9nU3RyZWFtTmFtZTogJ3h5eicsXG4gICAgfSwgdW5kZWZpbmVkLCByZXNwb25zZSk7XG5cbiAgICB0ZXN0LmRlZXBFcXVhbChKU09OLnBhcnNlKGVjck1vY2subGFzdFNldFJlcG9zaXRvcnlQb2xpY3lSZXF1ZXN0LnBvbGljeVRleHQpLCB7XG4gICAgICBWZXJzaW9uOiAnMjAwOC0xMC0xNycsXG4gICAgICBTdGF0ZW1lbnQ6IFtcbiAgICAgICAgeyBBY3Rpb246ICdib29tJyB9LFxuICAgICAgICB7IFNpZDogXCJTdGFja0lkXCIsIEVmZmVjdDogXCJEZW55XCIsIEFjdGlvbjogXCJPd25lZEJ5OkNES1N0YWNrXCIsIFByaW5jaXBhbDogXCIqXCIgfVxuICAgICAgXVxuICAgIH0pO1xuXG4gICAgdGVzdC5kZWVwRXF1YWwob3V0cHV0LCB7XG4gICAgICByZXNwb25zZVN0YXR1czogJ1NVQ0NFU1MnLFxuICAgICAgcmVhc29uOiAnT0snLFxuICAgICAgcGh5c0lkOiAnUmVwb3NpdG9yeU5hbWUnLFxuICAgICAgZGF0YToge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJ1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgYXN5bmMgJ2V4ZXJjaXNlIGhhbmRsZXIgY3JlYXRlIHdpdGggcG9saWN5IHdpdGggYXJyYXkgc3RhdGVtZW50Jyh0ZXN0OiBUZXN0KSB7XG4gICAgY29uc3QgaGFuZGxlciA9IHByb3h5cXVpcmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uJywgJ2xpYicsICdhZG9wdC1yZXBvc2l0b3J5JywgJ2hhbmRsZXInKSwge1xuICAgICAgJ2F3cy1zZGsnOiB7XG4gICAgICAgICdAbm9DYWxsVGhydSc6IHRydWUsXG4gICAgICAgIFwiRUNSXCI6IEVDUldpdGhFbXB0eVBvbGljeSxcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGxldCBvdXRwdXQ7XG4gICAgYXN5bmMgZnVuY3Rpb24gcmVzcG9uc2UocmVzcG9uc2VTdGF0dXM6IHN0cmluZywgcmVhc29uOiBzdHJpbmcsIHBoeXNJZDogc3RyaW5nLCBkYXRhOiBhbnkpIHtcbiAgICAgIG91dHB1dCA9IHsgcmVzcG9uc2VTdGF0dXMsIHJlYXNvbiwgcGh5c0lkLCBkYXRhIH07XG4gICAgfVxuXG4gICAgYXdhaXQgaGFuZGxlci5oYW5kbGVyKHtcbiAgICAgIFN0YWNrSWQ6ICdTdGFja0lkJyxcbiAgICAgIFJlc291cmNlUHJvcGVydGllczoge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgICAgUG9saWN5RG9jdW1lbnQ6IHtcbiAgICAgICAgICBTdGF0ZW1lbnQ6IFsgeyBBY3Rpb246ICdib29tJyB9LCB7IFJlc291cmNlOiBcImZvb1wiIH0gIF1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIFJlcXVlc3RUeXBlOiAnQ3JlYXRlJyxcbiAgICAgIFJlc3BvbnNlVVJMOiAnaHR0cHM6Ly9sb2NhbGhvc3QvdGVzdCdcbiAgICB9LCB7XG4gICAgICBsb2dTdHJlYW1OYW1lOiAneHl6JyxcbiAgICB9LCB1bmRlZmluZWQsIHJlc3BvbnNlKTtcblxuICAgIHRlc3QuZGVlcEVxdWFsKEpTT04ucGFyc2UoZWNyTW9jay5sYXN0U2V0UmVwb3NpdG9yeVBvbGljeVJlcXVlc3QucG9saWN5VGV4dCksIHtcbiAgICAgIFZlcnNpb246ICcyMDA4LTEwLTE3JyxcbiAgICAgIFN0YXRlbWVudDogW1xuICAgICAgICB7IEFjdGlvbjogXCJib29tXCIgfSxcbiAgICAgICAgeyBSZXNvdXJjZTogXCJmb29cIiB9LFxuICAgICAgICB7IFNpZDogXCJTdGFja0lkXCIsIEVmZmVjdDogXCJEZW55XCIsIEFjdGlvbjogXCJPd25lZEJ5OkNES1N0YWNrXCIsIFByaW5jaXBhbDogXCIqXCIgfVxuICAgICAgXVxuICAgIH0pO1xuXG4gICAgdGVzdC5kZWVwRXF1YWwob3V0cHV0LCB7XG4gICAgICByZXNwb25zZVN0YXR1czogJ1NVQ0NFU1MnLFxuICAgICAgcmVhc29uOiAnT0snLFxuICAgICAgcGh5c0lkOiAnUmVwb3NpdG9yeU5hbWUnLFxuICAgICAgZGF0YToge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJ1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgYXN5bmMgJ2V4ZXJjaXNlIGhhbmRsZXIgY3JlYXRlJyh0ZXN0OiBUZXN0KSB7XG4gICAgY29uc3QgaGFuZGxlciA9IHByb3h5cXVpcmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uJywgJ2xpYicsICdhZG9wdC1yZXBvc2l0b3J5JywgJ2hhbmRsZXInKSwge1xuICAgICAgJ2F3cy1zZGsnOiB7XG4gICAgICAgICdAbm9DYWxsVGhydSc6IHRydWUsXG4gICAgICAgIFwiRUNSXCI6IEVDUldpdGhFbXB0eVBvbGljeSxcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIGxldCBvdXRwdXQ7XG4gICAgYXN5bmMgZnVuY3Rpb24gcmVzcG9uc2UocmVzcG9uc2VTdGF0dXM6IHN0cmluZywgcmVhc29uOiBzdHJpbmcsIHBoeXNJZDogc3RyaW5nLCBkYXRhOiBhbnkpIHtcbiAgICAgIG91dHB1dCA9IHsgcmVzcG9uc2VTdGF0dXMsIHJlYXNvbiwgcGh5c0lkLCBkYXRhIH07XG4gICAgfVxuXG4gICAgYXdhaXQgaGFuZGxlci5oYW5kbGVyKHtcbiAgICAgIFN0YWNrSWQ6ICdTdGFja0lkJyxcbiAgICAgIFJlc291cmNlUHJvcGVydGllczoge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgIH0sXG4gICAgICBSZXF1ZXN0VHlwZTogJ0NyZWF0ZScsXG4gICAgICBSZXNwb25zZVVSTDogJ2h0dHBzOi8vbG9jYWxob3N0L3Rlc3QnXG4gICAgfSwge1xuICAgICAgbG9nU3RyZWFtTmFtZTogJ3h5eicsXG4gICAgfSwgdW5kZWZpbmVkLCByZXNwb25zZSk7XG5cbiAgICB0ZXN0LmRlZXBFcXVhbChvdXRwdXQsIHtcbiAgICAgIHJlc3BvbnNlU3RhdHVzOiAnU1VDQ0VTUycsXG4gICAgICByZWFzb246ICdPSycsXG4gICAgICBwaHlzSWQ6ICdSZXBvc2l0b3J5TmFtZScsXG4gICAgICBkYXRhOiB7XG4gICAgICAgIFJlcG9zaXRvcnlOYW1lOiAnUmVwb3NpdG9yeU5hbWUnXG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB0ZXN0LmRvbmUoKTtcbiAgfSxcblxuICBhc3luYyAnZXhlcmNpc2UgaGFuZGxlciBkZWxldGUnKHRlc3Q6IFRlc3QpIHtcbiAgICBjb25zdCBoYW5kbGVyID0gcHJveHlxdWlyZShwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi4nLCAnbGliJywgJ2Fkb3B0LXJlcG9zaXRvcnknLCAnaGFuZGxlcicpLCB7XG4gICAgICAnYXdzLXNkayc6IHsgJ0Bub0NhbGxUaHJ1JzogdHJ1ZSwgXCJFQ1JcIjogRUNSV2l0aE93bmluZ1BvbGljeSB9XG4gICAgfSk7XG5cbiAgICBsZXQgb3V0cHV0O1xuICAgIGFzeW5jIGZ1bmN0aW9uIHJlc3BvbnNlKHJlc3BvbnNlU3RhdHVzOiBzdHJpbmcsIHJlYXNvbjogc3RyaW5nLCBwaHlzSWQ6IHN0cmluZywgZGF0YTogYW55KSB7XG4gICAgICBvdXRwdXQgPSB7IHJlc3BvbnNlU3RhdHVzLCByZWFzb24sIHBoeXNJZCwgZGF0YSB9O1xuICAgIH1cblxuICAgIGF3YWl0IGhhbmRsZXIuaGFuZGxlcih7XG4gICAgICBTdGFja0lkOiAnU3RhY2tJZCcsXG4gICAgICBSZXNvdXJjZVByb3BlcnRpZXM6IHtcbiAgICAgICAgUmVwb3NpdG9yeU5hbWU6ICdSZXBvc2l0b3J5TmFtZScsXG4gICAgICB9LFxuICAgICAgUmVxdWVzdFR5cGU6ICdEZWxldGUnLFxuICAgICAgUmVzcG9uc2VVUkw6ICdodHRwczovL2xvY2FsaG9zdC90ZXN0J1xuICAgIH0sIHtcbiAgICAgIGxvZ1N0cmVhbU5hbWU6ICd4eXonLFxuICAgIH0sIHVuZGVmaW5lZCwgcmVzcG9uc2UpO1xuXG4gICAgdGVzdC5kZWVwRXF1YWwob3V0cHV0LCB7XG4gICAgICByZXNwb25zZVN0YXR1czogJ1NVQ0NFU1MnLFxuICAgICAgcmVhc29uOiAnT0snLFxuICAgICAgcGh5c0lkOiAnUmVwb3NpdG9yeU5hbWUnLFxuICAgICAgZGF0YToge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJ1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgYXN5bmMgJ2V4ZXJjaXNlIFwiZGVsZXRlXCIgaGFuZGxlciB3aGVuIHJlcG9zaXRvcnkgZG9lc250IGV4aXN0Jyh0ZXN0OiBUZXN0KSB7XG4gICAgY29uc3QgaGFuZGxlciA9IHByb3h5cXVpcmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uJywgJ2xpYicsICdhZG9wdC1yZXBvc2l0b3J5JywgJ2hhbmRsZXInKSwge1xuICAgICAgJ2F3cy1zZGsnOiB7ICdAbm9DYWxsVGhydSc6IHRydWUsIFwiRUNSXCI6IEVDUldpdGhSZXBvc2l0b3J5Tm90Rm91bmQgfVxuICAgIH0pO1xuXG4gICAgbGV0IG91dHB1dDtcbiAgICBhc3luYyBmdW5jdGlvbiByZXNwb25zZShyZXNwb25zZVN0YXR1czogc3RyaW5nLCByZWFzb246IHN0cmluZywgcGh5c0lkOiBzdHJpbmcsIGRhdGE6IGFueSkge1xuICAgICAgb3V0cHV0ID0geyByZXNwb25zZVN0YXR1cywgcmVhc29uLCBwaHlzSWQsIGRhdGEgfTtcbiAgICB9XG5cbiAgICBhd2FpdCBoYW5kbGVyLmhhbmRsZXIoe1xuICAgICAgU3RhY2tJZDogJ1N0YWNrSWQnLFxuICAgICAgUmVzb3VyY2VQcm9wZXJ0aWVzOiB7XG4gICAgICAgIFJlcG9zaXRvcnlOYW1lOiAnUmVwb3NpdG9yeU5hbWUnLFxuICAgICAgfSxcbiAgICAgIFJlcXVlc3RUeXBlOiAnRGVsZXRlJyxcbiAgICAgIFJlc3BvbnNlVVJMOiAnaHR0cHM6Ly9sb2NhbGhvc3QvdGVzdCdcbiAgICB9LCB7XG4gICAgICBsb2dTdHJlYW1OYW1lOiAneHl6JyxcbiAgICB9LCB1bmRlZmluZWQsIHJlc3BvbnNlKTtcblxuICAgIHRlc3QuZGVlcEVxdWFsKG91dHB1dCwge1xuICAgICAgcmVzcG9uc2VTdGF0dXM6ICdTVUNDRVNTJyxcbiAgICAgIHJlYXNvbjogJ09LJyxcbiAgICAgIHBoeXNJZDogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgIGRhdGE6IHtcbiAgICAgICAgUmVwb3NpdG9yeU5hbWU6ICdSZXBvc2l0b3J5TmFtZSdcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHRlc3QuZG9uZSgpO1xuICB9LFxuXG4gIGFzeW5jICdleGVyY2lzZSBcImNyZWF0ZVwiIGhhbmRsZXIgd2hlbiByZXBvc2l0b3J5IGRvZXNudCBleGlzdCcodGVzdDogVGVzdCkge1xuICAgIGNvbnN0IGhhbmRsZXIgPSBwcm94eXF1aXJlKHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuLicsICdsaWInLCAnYWRvcHQtcmVwb3NpdG9yeScsICdoYW5kbGVyJyksIHtcbiAgICAgICdhd3Mtc2RrJzogeyAnQG5vQ2FsbFRocnUnOiB0cnVlLCBcIkVDUlwiOiBFQ1JXaXRoUmVwb3NpdG9yeU5vdEZvdW5kIH1cbiAgICB9KTtcblxuICAgIGxldCBvdXRwdXQ7XG4gICAgYXN5bmMgZnVuY3Rpb24gcmVzcG9uc2UocmVzcG9uc2VTdGF0dXM6IHN0cmluZywgcmVhc29uOiBzdHJpbmcsIHBoeXNJZDogc3RyaW5nLCBkYXRhOiBhbnkpIHtcbiAgICAgIG91dHB1dCA9IHsgcmVzcG9uc2VTdGF0dXMsIHJlYXNvbiwgcGh5c0lkLCBkYXRhIH07XG4gICAgfVxuXG4gICAgYXdhaXQgaGFuZGxlci5oYW5kbGVyKHtcbiAgICAgIFN0YWNrSWQ6ICdTdGFja0lkJyxcbiAgICAgIFJlc291cmNlUHJvcGVydGllczoge1xuICAgICAgICBSZXBvc2l0b3J5TmFtZTogJ1JlcG9zaXRvcnlOYW1lJyxcbiAgICAgIH0sXG4gICAgICBSZXF1ZXN0VHlwZTogJ0NyZWF0ZScsXG4gICAgICBSZXNwb25zZVVSTDogJ2h0dHBzOi8vbG9jYWxob3N0L3Rlc3QnXG4gICAgfSwge1xuICAgICAgbG9nU3RyZWFtTmFtZTogJ3h5eicsXG4gICAgfSwgdW5kZWZpbmVkLCByZXNwb25zZSk7XG5cbiAgICB0ZXN0LmRlZXBFcXVhbChvdXRwdXQsIHtcbiAgICAgIHJlc3BvbnNlU3RhdHVzOiAnRkFJTEVEJyxcbiAgICAgIHJlYXNvbjogJ1NpbXVsYXRlZCBSZXBvc2l0b3J5UG9saWN5Tm90Rm91bmRFeGNlcHRpb24nLFxuICAgICAgcGh5c0lkOiAneHl6JyxcbiAgICAgIGRhdGE6IHsgfVxuICAgIH0pO1xuXG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG59O1xuXG5mdW5jdGlvbiBFQ1JXaXRoRW1wdHlQb2xpY3koKSB7XG4gIGVjck1vY2sgPSBuZXcgRUNSKHsgYXNkZjogJ2FzZGYnIH0pO1xuICByZXR1cm4gZWNyTW9jaztcbn1cblxuZnVuY3Rpb24gRUNSV2l0aE93bmluZ1BvbGljeSgpIHtcbiAgcmV0dXJuIG5ldyBFQ1Ioe1xuICAgIFN0YXRlbWVudDogW1xuICAgICAge1xuICAgICAgICBTaWQ6ICdTdGFja0lkJyxcbiAgICAgICAgRWZmZWN0OiBcIkRlbnlcIixcbiAgICAgICAgQWN0aW9uOiBcIk93bmVkQnk6Q0RLU3RhY2tcIixcbiAgICAgICAgUHJpbmNpcGFsOiBcIipcIlxuICAgICAgfVxuICAgIF1cbiAgfSk7XG59XG5cbmZ1bmN0aW9uIEVDUldpdGhSZXBvc2l0b3J5Tm90Rm91bmQoKSB7XG4gIGNvbnN0IGVjciA9IG5ldyBFQ1Ioe30pO1xuICBlY3Iuc2hvdWxkVGhyb3dOb3RGb3VuZCA9IHRydWU7XG4gIHJldHVybiBlY3I7XG59XG5cbmNsYXNzIEVDUiB7XG4gIHB1YmxpYyBsYXN0U2V0UmVwb3NpdG9yeVBvbGljeVJlcXVlc3Q6IGFueTtcbiAgcHVibGljIHNob3VsZFRocm93Tm90Rm91bmQgPSBmYWxzZTtcblxuICBwdWJsaWMgY29uc3RydWN0b3IocHJpdmF0ZSBwb2xpY3k6IGFueSkge1xuICB9XG5cbiAgcHVibGljIGdldFJlcG9zaXRvcnlQb2xpY3koKSB7XG4gICAgY29uc3Qgc2VsZiA9IHRoaXM7XG4gICAgcmV0dXJuIHtcbiAgICAgIGFzeW5jIHByb21pc2UoKSB7XG4gICAgICAgIGlmIChzZWxmLnNob3VsZFRocm93Tm90Rm91bmQpIHsgcmV0dXJuIHNlbGYudGhyb3dOb3RGb3VuZCgpOyB9XG4gICAgICAgIHJldHVybiB7IHBvbGljeVRleHQ6IEpTT04uc3RyaW5naWZ5KHNlbGYucG9saWN5KSB9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICBwdWJsaWMgc2V0UmVwb3NpdG9yeVBvbGljeShyZXE6IGFueSkge1xuICAgIGNvbnN0IHNlbGYgPSB0aGlzO1xuICAgIHRoaXMubGFzdFNldFJlcG9zaXRvcnlQb2xpY3lSZXF1ZXN0ID0gcmVxO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIGFzeW5jIHByb21pc2UoKSB7XG4gICAgICAgIGlmIChzZWxmLnNob3VsZFRocm93Tm90Rm91bmQpIHsgcmV0dXJuIHNlbGYudGhyb3dOb3RGb3VuZCgpOyB9XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICB9O1xuICB9XG5cbiAgcHVibGljIGxpc3RJbWFnZXMoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGFzeW5jIHByb21pc2UoKSB7XG4gICAgICAgIHJldHVybiB7IGltYWdlSWRzOiBbXSB9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICBwdWJsaWMgYmF0Y2hEZWxldGVJbWFnZSgpIHtcbiAgICBjb25zdCBzZWxmID0gdGhpcztcbiAgICByZXR1cm4ge1xuICAgICAgYXN5bmMgcHJvbWlzZSgpIHtcbiAgICAgICAgaWYgKHNlbGYuc2hvdWxkVGhyb3dOb3RGb3VuZCkgeyByZXR1cm4gc2VsZi50aHJvd05vdEZvdW5kKCk7IH1cbiAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICBwdWJsaWMgZGVsZXRlUmVwb3NpdG9yeSgpIHtcbiAgICBjb25zdCBzZWxmID0gdGhpcztcbiAgICByZXR1cm4ge1xuICAgICAgYXN5bmMgcHJvbWlzZSgpIHtcbiAgICAgICAgaWYgKHNlbGYuc2hvdWxkVGhyb3dOb3RGb3VuZCkgeyByZXR1cm4gc2VsZi50aHJvd05vdEZvdW5kKCk7IH1cbiAgICAgICAgcmV0dXJuIHt9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICBwcml2YXRlIHRocm93Tm90Rm91bmQoKSB7XG4gICAgY29uc3QgZXJyID0gbmV3IEVycm9yKCdTaW11bGF0ZWQgUmVwb3NpdG9yeVBvbGljeU5vdEZvdW5kRXhjZXB0aW9uJyk7XG4gICAgKGVyciBhcyBhbnkpLmNvZGUgPSAnUmVwb3NpdG9yeVBvbGljeU5vdEZvdW5kRXhjZXB0aW9uJztcbiAgICB0aHJvdyBlcnI7XG4gIH1cbn1cbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.d.ts b/packages/@aws-cdk/assets-docker/test/test.image-asset.d.ts new file mode 100644 index 0000000000000..b64d61c49d36a --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.d.ts @@ -0,0 +1,11 @@ +import { Test } from 'nodeunit'; +declare const _default: { + 'test instantiating Asset Image'(test: Test): void; + 'with build args'(test: Test): void; + 'asset.repository.grantPull can be used to grant a principal permissions to use the image'(test: Test): void; + 'asset.repository.addToResourcePolicy can be used to modify the ECR resource policy via the adoption custom resource'(test: Test): void; + 'fails if the directory does not exist'(test: Test): void; + 'fails if the directory does not contain a Dockerfile'(test: Test): void; + 'docker directory is staged if asset staging is enabled'(test: Test): void; +}; +export = _default; diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.js b/packages/@aws-cdk/assets-docker/test/test.image-asset.js new file mode 100644 index 0000000000000..e0dca615a7f2d --- /dev/null +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.js @@ -0,0 +1,156 @@ +"use strict"; +const assert_1 = require("@aws-cdk/assert"); +const iam = require("@aws-cdk/aws-iam"); +const cdk = require("@aws-cdk/cdk"); +const fs = require("fs"); +const path = require("path"); +const lib_1 = require("../lib"); +module.exports = { + 'test instantiating Asset Image'(test) { + // GIVEN + const stack = new cdk.Stack(); + // WHEN + new lib_1.DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image'), + }); + // THEN + const template = assert_1.SynthUtils.synthesize(stack).template; + test.deepEqual(template.Parameters.ImageImageName5E684353, { + Type: 'String', + Description: 'ECR repository name and tag asset "Image"' + }); + test.done(); + }, + 'with build args'(test) { + // GIVEN + const stack = new cdk.Stack(); + // WHEN + const asset = new lib_1.DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image'), + buildArgs: { + a: 'b' + } + }); + // THEN + const assetMetadata = asset.node.metadata.find(({ type }) => type === 'aws:cdk:asset'); + test.deepEqual(assetMetadata && assetMetadata.data.buildArgs, { a: 'b' }); + test.done(); + }, + 'asset.repository.grantPull can be used to grant a principal permissions to use the image'(test) { + // GIVEN + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'MyUser'); + const asset = new lib_1.DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image') + }); + // WHEN + asset.repository.grantPull(user); + // THEN + assert_1.expect(stack).to(assert_1.haveResource('AWS::IAM::Policy', { + PolicyDocument: { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { "Ref": "AWS::Partition" }, + ":ecr:", + { "Ref": "AWS::Region" }, + ":", + { "Ref": "AWS::AccountId" }, + ":repository/", + { "Fn::GetAtt": ["ImageAdoptRepositoryE1E84E35", "RepositoryName"] } + ] + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyUserDefaultPolicy7B897426", + "Users": [ + { + "Ref": "MyUserDC45028B" + } + ] + })); + test.done(); + }, + 'asset.repository.addToResourcePolicy can be used to modify the ECR resource policy via the adoption custom resource'(test) { + // GIVEN + const stack = new cdk.Stack(); + const asset = new lib_1.DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image') + }); + // WHEN + asset.repository.addToResourcePolicy(new iam.PolicyStatement() + .addAction('BOOM') + .addPrincipal(new iam.ServicePrincipal('test.service'))); + // THEN + assert_1.expect(stack).to(assert_1.haveResource('Custom::ECRAdoptedRepository', { + "RepositoryName": { + "Fn::Select": [0, { "Fn::Split": ["@sha256:", { "Ref": "ImageImageName5E684353" }] }] + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "BOOM", + "Effect": "Allow", + "Principal": { + "Service": "test.service" + } + } + ], + "Version": "2012-10-17" + } + })); + test.done(); + }, + 'fails if the directory does not exist'(test) { + // GIVEN + const stack = new cdk.Stack(); + // THEN + test.throws(() => { + new lib_1.DockerImageAsset(stack, 'MyAsset', { + directory: `/does/not/exist/${Math.floor(Math.random() * 9999)}` + }); + }, /Cannot find image directory at/); + test.done(); + }, + 'fails if the directory does not contain a Dockerfile'(test) { + // GIVEN + const stack = new cdk.Stack(); + // THEN + test.throws(() => { + new lib_1.DockerImageAsset(stack, 'Asset', { + directory: __dirname + }); + }, /No 'Dockerfile' found in/); + test.done(); + }, + 'docker directory is staged if asset staging is enabled'(test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + new lib_1.DockerImageAsset(stack, 'MyAsset', { + directory: path.join(__dirname, 'demo-image') + }); + const session = app.synth(); + test.ok(fs.existsSync(path.join(session.directory, 'asset.1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/Dockerfile'))); + test.ok(fs.existsSync(path.join(session.directory, 'asset.1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/index.py'))); + test.done(); + } +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5pbWFnZS1hc3NldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QuaW1hZ2UtYXNzZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLDRDQUFtRTtBQUNuRSx3Q0FBeUM7QUFDekMsb0NBQXFDO0FBQ3JDLHlCQUEwQjtBQUUxQiw2QkFBOEI7QUFDOUIsZ0NBQTBDO0FBSTFDLGlCQUFTO0lBQ1AsZ0NBQWdDLENBQUMsSUFBVTtRQUN6QyxRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLElBQUksc0JBQWdCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRTtZQUNuQyxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDO1NBQzlDLENBQUMsQ0FBQztRQUVILE9BQU87UUFDUCxNQUFNLFFBQVEsR0FBRyxtQkFBVSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUM7UUFFdkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLHNCQUFzQixFQUFFO1lBQ3pELElBQUksRUFBRSxRQUFRO1lBQ2QsV0FBVyxFQUFFLDJDQUEyQztTQUN6RCxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDZCxDQUFDO0lBRUQsaUJBQWlCLENBQUMsSUFBVTtRQUMxQixRQUFRO1FBQ1IsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFOUIsT0FBTztRQUNQLE1BQU0sS0FBSyxHQUFHLElBQUksc0JBQWdCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRTtZQUNqRCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDO1lBQzdDLFNBQVMsRUFBRTtnQkFDVCxDQUFDLEVBQUUsR0FBRzthQUNQO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsT0FBTztRQUNQLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLElBQUksS0FBSyxlQUFlLENBQUMsQ0FBQztRQUN2RixJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsSUFBSSxhQUFhLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO1FBQzFFLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCwwRkFBMEYsQ0FBQyxJQUFVO1FBQ25HLFFBQVE7UUFDUixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUM5QixNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzNDLE1BQU0sS0FBSyxHQUFHLElBQUksc0JBQWdCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRTtZQUNqRCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDO1NBQzlDLENBQUMsQ0FBQztRQUVILE9BQU87UUFDUCxLQUFLLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVqQyxPQUFPO1FBQ1AsZUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxxQkFBWSxDQUFDLGtCQUFrQixFQUFFO1lBQ2hELGNBQWMsRUFBRTtnQkFDZCxXQUFXLEVBQUU7b0JBQ1g7d0JBQ0UsUUFBUSxFQUFFOzRCQUNSLGlDQUFpQzs0QkFDakMsNEJBQTRCOzRCQUM1QixtQkFBbUI7eUJBQ3BCO3dCQUNELFFBQVEsRUFBRSxPQUFPO3dCQUNqQixVQUFVLEVBQUU7NEJBQ1YsVUFBVSxFQUFFO2dDQUNWLEVBQUU7Z0NBQ0Y7b0NBQ0UsTUFBTTtvQ0FDTixFQUFFLEtBQUssRUFBRSxnQkFBZ0IsRUFBRTtvQ0FDM0IsT0FBTztvQ0FDUCxFQUFFLEtBQUssRUFBRSxhQUFhLEVBQUU7b0NBQ3hCLEdBQUc7b0NBQ0gsRUFBRSxLQUFLLEVBQUUsZ0JBQWdCLEVBQUU7b0NBQzNCLGNBQWM7b0NBQ2QsRUFBRSxZQUFZLEVBQUUsQ0FBRSw4QkFBOEIsRUFBRSxnQkFBZ0IsQ0FBRSxFQUFFO2lDQUN2RTs2QkFDRjt5QkFDRjtxQkFDRjtvQkFDRDt3QkFDRSxRQUFRLEVBQUUsMkJBQTJCO3dCQUNyQyxRQUFRLEVBQUUsT0FBTzt3QkFDakIsVUFBVSxFQUFFLEdBQUc7cUJBQ2hCO2lCQUNGO2dCQUNELFNBQVMsRUFBRSxZQUFZO2FBQ3hCO1lBQ0QsWUFBWSxFQUFFLDZCQUE2QjtZQUMzQyxPQUFPLEVBQUU7Z0JBQ1A7b0JBQ0UsS0FBSyxFQUFFLGdCQUFnQjtpQkFDeEI7YUFDRjtTQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUosSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztJQUVELHFIQUFxSCxDQUFDLElBQVU7UUFDOUgsUUFBUTtRQUNSLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzlCLE1BQU0sS0FBSyxHQUFHLElBQUksc0JBQWdCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRTtZQUNqRCxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDO1NBQzlDLENBQUMsQ0FBQztRQUVILE9BQU87UUFDUCxLQUFLLENBQUMsVUFBVSxDQUFDLG1CQUFtQixDQUFDLElBQUksR0FBRyxDQUFDLGVBQWUsRUFBRTthQUMzRCxTQUFTLENBQUMsTUFBTSxDQUFDO2FBQ2pCLFlBQVksQ0FBQyxJQUFJLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFM0QsT0FBTztRQUNQLGVBQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLENBQUMscUJBQVksQ0FBQyw4QkFBOEIsRUFBRTtZQUM1RCxnQkFBZ0IsRUFBRTtnQkFDaEIsWUFBWSxFQUFFLENBQUUsQ0FBQyxFQUFFLEVBQUUsV0FBVyxFQUFFLENBQUUsVUFBVSxFQUFFLEVBQUUsS0FBSyxFQUFFLHdCQUF3QixFQUFFLENBQUUsRUFBRSxDQUFFO2FBQzFGO1lBQ0QsZ0JBQWdCLEVBQUU7Z0JBQ2hCLFdBQVcsRUFBRTtvQkFDWDt3QkFDRSxRQUFRLEVBQUUsTUFBTTt3QkFDaEIsUUFBUSxFQUFFLE9BQU87d0JBQ2pCLFdBQVcsRUFBRTs0QkFDWCxTQUFTLEVBQUUsY0FBYzt5QkFDMUI7cUJBQ0Y7aUJBQ0Y7Z0JBQ0QsU0FBUyxFQUFFLFlBQVk7YUFDeEI7U0FDRixDQUFDLENBQUMsQ0FBQztRQUVKLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7SUFFRCx1Q0FBdUMsQ0FBQyxJQUFVO1FBQ2hELFFBQVE7UUFDUixNQUFNLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUU5QixPQUFPO1FBQ1AsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUU7WUFDZixJQUFJLHNCQUFnQixDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUU7Z0JBQ3JDLFNBQVMsRUFBRSxtQkFBbUIsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLEVBQUU7YUFDakUsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxFQUFFLGdDQUFnQyxDQUFDLENBQUM7UUFDckMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztJQUVELHNEQUFzRCxDQUFDLElBQVU7UUFDL0QsUUFBUTtRQUNSLE1BQU0sS0FBSyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBRTlCLE9BQU87UUFDUCxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRTtZQUNmLElBQUksc0JBQWdCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRTtnQkFDbkMsU0FBUyxFQUFFLFNBQVM7YUFDckIsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxFQUFFLDBCQUEwQixDQUFDLENBQUM7UUFDL0IsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztJQUVELHdEQUF3RCxDQUFDLElBQVU7UUFDakUsTUFBTSxHQUFHLEdBQUcsSUFBSSxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDMUIsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUUxQyxJQUFJLHNCQUFnQixDQUFDLEtBQUssRUFBRSxTQUFTLEVBQUU7WUFDckMsU0FBUyxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFlBQVksQ0FBQztTQUM5QyxDQUFDLENBQUM7UUFFSCxNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFFNUIsSUFBSSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxtRkFBbUYsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUMxSSxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLGlGQUFpRixDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hJLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNkLENBQUM7Q0FDRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZXhwZWN0LCBoYXZlUmVzb3VyY2UsIFN5bnRoVXRpbHMgfSBmcm9tICdAYXdzLWNkay9hc3NlcnQnO1xuaW1wb3J0IGlhbSA9IHJlcXVpcmUoJ0Bhd3MtY2RrL2F3cy1pYW0nKTtcbmltcG9ydCBjZGsgPSByZXF1aXJlKCdAYXdzLWNkay9jZGsnKTtcbmltcG9ydCBmcyA9IHJlcXVpcmUoJ2ZzJyk7XG5pbXBvcnQgeyBUZXN0IH0gZnJvbSAnbm9kZXVuaXQnO1xuaW1wb3J0IHBhdGggPSByZXF1aXJlKCdwYXRoJyk7XG5pbXBvcnQgeyBEb2NrZXJJbWFnZUFzc2V0IH0gZnJvbSAnLi4vbGliJztcblxuLy8gdHNsaW50OmRpc2FibGU6b2JqZWN0LWxpdGVyYWwta2V5LXF1b3Rlc1xuXG5leHBvcnQgPSB7XG4gICd0ZXN0IGluc3RhbnRpYXRpbmcgQXNzZXQgSW1hZ2UnKHRlc3Q6IFRlc3QpIHtcbiAgICAvLyBHSVZFTlxuICAgIGNvbnN0IHN0YWNrID0gbmV3IGNkay5TdGFjaygpO1xuXG4gICAgLy8gV0hFTlxuICAgIG5ldyBEb2NrZXJJbWFnZUFzc2V0KHN0YWNrLCAnSW1hZ2UnLCB7XG4gICAgICBkaXJlY3Rvcnk6IHBhdGguam9pbihfX2Rpcm5hbWUsICdkZW1vLWltYWdlJyksXG4gICAgfSk7XG5cbiAgICAvLyBUSEVOXG4gICAgY29uc3QgdGVtcGxhdGUgPSBTeW50aFV0aWxzLnN5bnRoZXNpemUoc3RhY2spLnRlbXBsYXRlO1xuXG4gICAgdGVzdC5kZWVwRXF1YWwodGVtcGxhdGUuUGFyYW1ldGVycy5JbWFnZUltYWdlTmFtZTVFNjg0MzUzLCB7XG4gICAgICBUeXBlOiAnU3RyaW5nJyxcbiAgICAgIERlc2NyaXB0aW9uOiAnRUNSIHJlcG9zaXRvcnkgbmFtZSBhbmQgdGFnIGFzc2V0IFwiSW1hZ2VcIidcbiAgICB9KTtcblxuICAgIHRlc3QuZG9uZSgpO1xuICB9LFxuXG4gICd3aXRoIGJ1aWxkIGFyZ3MnKHRlc3Q6IFRlc3QpIHtcbiAgICAvLyBHSVZFTlxuICAgIGNvbnN0IHN0YWNrID0gbmV3IGNkay5TdGFjaygpO1xuXG4gICAgLy8gV0hFTlxuICAgIGNvbnN0IGFzc2V0ID0gbmV3IERvY2tlckltYWdlQXNzZXQoc3RhY2ssICdJbWFnZScsIHtcbiAgICAgIGRpcmVjdG9yeTogcGF0aC5qb2luKF9fZGlybmFtZSwgJ2RlbW8taW1hZ2UnKSxcbiAgICAgIGJ1aWxkQXJnczoge1xuICAgICAgICBhOiAnYidcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIC8vIFRIRU5cbiAgICBjb25zdCBhc3NldE1ldGFkYXRhID0gYXNzZXQubm9kZS5tZXRhZGF0YS5maW5kKCh7IHR5cGUgfSkgPT4gdHlwZSA9PT0gJ2F3czpjZGs6YXNzZXQnKTtcbiAgICB0ZXN0LmRlZXBFcXVhbChhc3NldE1ldGFkYXRhICYmIGFzc2V0TWV0YWRhdGEuZGF0YS5idWlsZEFyZ3MsIHsgYTogJ2InIH0pO1xuICAgIHRlc3QuZG9uZSgpO1xuICB9LFxuXG4gICdhc3NldC5yZXBvc2l0b3J5LmdyYW50UHVsbCBjYW4gYmUgdXNlZCB0byBncmFudCBhIHByaW5jaXBhbCBwZXJtaXNzaW9ucyB0byB1c2UgdGhlIGltYWdlJyh0ZXN0OiBUZXN0KSB7XG4gICAgLy8gR0lWRU5cbiAgICBjb25zdCBzdGFjayA9IG5ldyBjZGsuU3RhY2soKTtcbiAgICBjb25zdCB1c2VyID0gbmV3IGlhbS5Vc2VyKHN0YWNrLCAnTXlVc2VyJyk7XG4gICAgY29uc3QgYXNzZXQgPSBuZXcgRG9ja2VySW1hZ2VBc3NldChzdGFjaywgJ0ltYWdlJywge1xuICAgICAgZGlyZWN0b3J5OiBwYXRoLmpvaW4oX19kaXJuYW1lLCAnZGVtby1pbWFnZScpXG4gICAgfSk7XG5cbiAgICAvLyBXSEVOXG4gICAgYXNzZXQucmVwb3NpdG9yeS5ncmFudFB1bGwodXNlcik7XG5cbiAgICAvLyBUSEVOXG4gICAgZXhwZWN0KHN0YWNrKS50byhoYXZlUmVzb3VyY2UoJ0FXUzo6SUFNOjpQb2xpY3knLCB7XG4gICAgICBQb2xpY3lEb2N1bWVudDoge1xuICAgICAgICBcIlN0YXRlbWVudFwiOiBbXG4gICAgICAgICAge1xuICAgICAgICAgICAgXCJBY3Rpb25cIjogW1xuICAgICAgICAgICAgICBcImVjcjpCYXRjaENoZWNrTGF5ZXJBdmFpbGFiaWxpdHlcIixcbiAgICAgICAgICAgICAgXCJlY3I6R2V0RG93bmxvYWRVcmxGb3JMYXllclwiLFxuICAgICAgICAgICAgICBcImVjcjpCYXRjaEdldEltYWdlXCJcbiAgICAgICAgICAgIF0sXG4gICAgICAgICAgICBcIkVmZmVjdFwiOiBcIkFsbG93XCIsXG4gICAgICAgICAgICBcIlJlc291cmNlXCI6IHtcbiAgICAgICAgICAgICAgXCJGbjo6Sm9pblwiOiBbXG4gICAgICAgICAgICAgICAgXCJcIixcbiAgICAgICAgICAgICAgICBbXG4gICAgICAgICAgICAgICAgICBcImFybjpcIixcbiAgICAgICAgICAgICAgICAgIHsgXCJSZWZcIjogXCJBV1M6OlBhcnRpdGlvblwiIH0sXG4gICAgICAgICAgICAgICAgICBcIjplY3I6XCIsXG4gICAgICAgICAgICAgICAgICB7IFwiUmVmXCI6IFwiQVdTOjpSZWdpb25cIiB9LFxuICAgICAgICAgICAgICAgICAgXCI6XCIsXG4gICAgICAgICAgICAgICAgICB7IFwiUmVmXCI6IFwiQVdTOjpBY2NvdW50SWRcIiB9LFxuICAgICAgICAgICAgICAgICAgXCI6cmVwb3NpdG9yeS9cIixcbiAgICAgICAgICAgICAgICAgIHsgXCJGbjo6R2V0QXR0XCI6IFsgXCJJbWFnZUFkb3B0UmVwb3NpdG9yeUUxRTg0RTM1XCIsIFwiUmVwb3NpdG9yeU5hbWVcIiBdIH1cbiAgICAgICAgICAgICAgICBdXG4gICAgICAgICAgICAgIF1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIHtcbiAgICAgICAgICAgIFwiQWN0aW9uXCI6IFwiZWNyOkdldEF1dGhvcml6YXRpb25Ub2tlblwiLFxuICAgICAgICAgICAgXCJFZmZlY3RcIjogXCJBbGxvd1wiLFxuICAgICAgICAgICAgXCJSZXNvdXJjZVwiOiBcIipcIlxuICAgICAgICAgIH1cbiAgICAgICAgXSxcbiAgICAgICAgXCJWZXJzaW9uXCI6IFwiMjAxMi0xMC0xN1wiXG4gICAgICB9LFxuICAgICAgXCJQb2xpY3lOYW1lXCI6IFwiTXlVc2VyRGVmYXVsdFBvbGljeTdCODk3NDI2XCIsXG4gICAgICBcIlVzZXJzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwiUmVmXCI6IFwiTXlVc2VyREM0NTAyOEJcIlxuICAgICAgICB9XG4gICAgICBdXG4gICAgfSkpO1xuXG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgJ2Fzc2V0LnJlcG9zaXRvcnkuYWRkVG9SZXNvdXJjZVBvbGljeSBjYW4gYmUgdXNlZCB0byBtb2RpZnkgdGhlIEVDUiByZXNvdXJjZSBwb2xpY3kgdmlhIHRoZSBhZG9wdGlvbiBjdXN0b20gcmVzb3VyY2UnKHRlc3Q6IFRlc3QpIHtcbiAgICAvLyBHSVZFTlxuICAgIGNvbnN0IHN0YWNrID0gbmV3IGNkay5TdGFjaygpO1xuICAgIGNvbnN0IGFzc2V0ID0gbmV3IERvY2tlckltYWdlQXNzZXQoc3RhY2ssICdJbWFnZScsIHtcbiAgICAgIGRpcmVjdG9yeTogcGF0aC5qb2luKF9fZGlybmFtZSwgJ2RlbW8taW1hZ2UnKVxuICAgIH0pO1xuXG4gICAgLy8gV0hFTlxuICAgIGFzc2V0LnJlcG9zaXRvcnkuYWRkVG9SZXNvdXJjZVBvbGljeShuZXcgaWFtLlBvbGljeVN0YXRlbWVudCgpXG4gICAgICAuYWRkQWN0aW9uKCdCT09NJylcbiAgICAgIC5hZGRQcmluY2lwYWwobmV3IGlhbS5TZXJ2aWNlUHJpbmNpcGFsKCd0ZXN0LnNlcnZpY2UnKSkpO1xuXG4gICAgLy8gVEhFTlxuICAgIGV4cGVjdChzdGFjaykudG8oaGF2ZVJlc291cmNlKCdDdXN0b206OkVDUkFkb3B0ZWRSZXBvc2l0b3J5Jywge1xuICAgICAgXCJSZXBvc2l0b3J5TmFtZVwiOiB7XG4gICAgICAgIFwiRm46OlNlbGVjdFwiOiBbIDAsIHsgXCJGbjo6U3BsaXRcIjogWyBcIkBzaGEyNTY6XCIsIHsgXCJSZWZcIjogXCJJbWFnZUltYWdlTmFtZTVFNjg0MzUzXCIgfSBdIH0gXVxuICAgICAgfSxcbiAgICAgIFwiUG9saWN5RG9jdW1lbnRcIjoge1xuICAgICAgICBcIlN0YXRlbWVudFwiOiBbXG4gICAgICAgICAge1xuICAgICAgICAgICAgXCJBY3Rpb25cIjogXCJCT09NXCIsXG4gICAgICAgICAgICBcIkVmZmVjdFwiOiBcIkFsbG93XCIsXG4gICAgICAgICAgICBcIlByaW5jaXBhbFwiOiB7XG4gICAgICAgICAgICAgIFwiU2VydmljZVwiOiBcInRlc3Quc2VydmljZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICBdLFxuICAgICAgICBcIlZlcnNpb25cIjogXCIyMDEyLTEwLTE3XCJcbiAgICAgIH1cbiAgICB9KSk7XG5cbiAgICB0ZXN0LmRvbmUoKTtcbiAgfSxcblxuICAnZmFpbHMgaWYgdGhlIGRpcmVjdG9yeSBkb2VzIG5vdCBleGlzdCcodGVzdDogVGVzdCkge1xuICAgIC8vIEdJVkVOXG4gICAgY29uc3Qgc3RhY2sgPSBuZXcgY2RrLlN0YWNrKCk7XG5cbiAgICAvLyBUSEVOXG4gICAgdGVzdC50aHJvd3MoKCkgPT4ge1xuICAgICAgbmV3IERvY2tlckltYWdlQXNzZXQoc3RhY2ssICdNeUFzc2V0Jywge1xuICAgICAgICBkaXJlY3Rvcnk6IGAvZG9lcy9ub3QvZXhpc3QvJHtNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiA5OTk5KX1gXG4gICAgICB9KTtcbiAgICB9LCAvQ2Fubm90IGZpbmQgaW1hZ2UgZGlyZWN0b3J5IGF0Lyk7XG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgJ2ZhaWxzIGlmIHRoZSBkaXJlY3RvcnkgZG9lcyBub3QgY29udGFpbiBhIERvY2tlcmZpbGUnKHRlc3Q6IFRlc3QpIHtcbiAgICAvLyBHSVZFTlxuICAgIGNvbnN0IHN0YWNrID0gbmV3IGNkay5TdGFjaygpO1xuXG4gICAgLy8gVEhFTlxuICAgIHRlc3QudGhyb3dzKCgpID0+IHtcbiAgICAgIG5ldyBEb2NrZXJJbWFnZUFzc2V0KHN0YWNrLCAnQXNzZXQnLCB7XG4gICAgICAgIGRpcmVjdG9yeTogX19kaXJuYW1lXG4gICAgICB9KTtcbiAgICB9LCAvTm8gJ0RvY2tlcmZpbGUnIGZvdW5kIGluLyk7XG4gICAgdGVzdC5kb25lKCk7XG4gIH0sXG5cbiAgJ2RvY2tlciBkaXJlY3RvcnkgaXMgc3RhZ2VkIGlmIGFzc2V0IHN0YWdpbmcgaXMgZW5hYmxlZCcodGVzdDogVGVzdCkge1xuICAgIGNvbnN0IGFwcCA9IG5ldyBjZGsuQXBwKCk7XG4gICAgY29uc3Qgc3RhY2sgPSBuZXcgY2RrLlN0YWNrKGFwcCwgJ3N0YWNrJyk7XG5cbiAgICBuZXcgRG9ja2VySW1hZ2VBc3NldChzdGFjaywgJ015QXNzZXQnLCB7XG4gICAgICBkaXJlY3Rvcnk6IHBhdGguam9pbihfX2Rpcm5hbWUsICdkZW1vLWltYWdlJylcbiAgICB9KTtcblxuICAgIGNvbnN0IHNlc3Npb24gPSBhcHAuc3ludGgoKTtcblxuICAgIHRlc3Qub2soZnMuZXhpc3RzU3luYyhwYXRoLmpvaW4oc2Vzc2lvbi5kaXJlY3RvcnksICdhc3NldC4xYTE3YTE0MTUwNWFjNjkxNDQ5MzFmZTI2M2QxMzBmNDYxMjI1MWNhYTRiYmJkYWY2OGE0NGVkMGY0MDU0MzljL0RvY2tlcmZpbGUnKSkpO1xuICAgIHRlc3Qub2soZnMuZXhpc3RzU3luYyhwYXRoLmpvaW4oc2Vzc2lvbi5kaXJlY3RvcnksICdhc3NldC4xYTE3YTE0MTUwNWFjNjkxNDQ5MzFmZTI2M2QxMzBmNDYxMjI1MWNhYTRiYmJkYWY2OGE0NGVkMGY0MDU0MzljL2luZGV4LnB5JykpKTtcbiAgICB0ZXN0LmRvbmUoKTtcbiAgfVxufTtcbiJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/tsconfig.json b/packages/@aws-cdk/assets-docker/tsconfig.json new file mode 100644 index 0000000000000..df4fbecba7374 --- /dev/null +++ b/packages/@aws-cdk/assets-docker/tsconfig.json @@ -0,0 +1,72 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2016", + "es2017.object", + "es2017.string" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "stripInternal": true, + "target": "ES2018", + "composite": true + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "../assets" + }, + { + "path": "../aws-cloudformation" + }, + { + "path": "../aws-ecr" + }, + { + "path": "../aws-iam" + }, + { + "path": "../aws-lambda" + }, + { + "path": "../aws-s3" + }, + { + "path": "../cdk" + }, + { + "path": "../cx-api" + }, + { + "path": "../assert" + }, + { + "path": "../../../tools/cdk-build-tools" + }, + { + "path": "../../../tools/pkglint" + } + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} diff --git a/packages/@aws-cdk/assets/package-lock.json b/packages/@aws-cdk/assets/package-lock.json index 7ea35fae94b35..d11f3fe1e66c3 100644 --- a/packages/@aws-cdk/assets/package-lock.json +++ b/packages/@aws-cdk/assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index ad3523064e5de..98a5250d6a47e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,4 +1,4 @@ -import { Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource as IResourceBase, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from "./resource"; import { RestApi } from './restapi'; @@ -70,7 +70,7 @@ export interface ApiKeyProps extends ResourceOptions { * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-name * @default automically generated name */ - readonly name?: string; + readonly apiKeyName?: PhysicalName; } /** @@ -83,18 +83,20 @@ export class ApiKey extends Resource implements IApiKey { public readonly keyId: string; constructor(scope: Construct, id: string, props: ApiKeyProps = { }) { - super(scope, id); + super(scope, id, { + physicalName: props.apiKeyName, + }); const resource = new CfnApiKey(this, 'Resource', { customerId: props.customerId, description: props.description, enabled: props.enabled || true, generateDistinctId: props.generateDistinctId, - name: props.name, + name: this.physicalName, stageKeys: this.renderStageKeys(props.resources) }); - this.keyId = resource.refAsString; + this.keyId = resource.ref; } private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined { diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index e7f0d0ae7dbe7..df826231200f1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -1,4 +1,4 @@ -import { Construct, DeletionPolicy, Lazy, Resource, Stack } from '@aws-cdk/cdk'; +import { CfnDeletionPolicy, Construct, Lazy, Resource, Stack } from '@aws-cdk/cdk'; import crypto = require('crypto'); import { CfnDeployment, CfnDeploymentProps } from './apigateway.generated'; import { IRestApi } from './restapi'; @@ -72,11 +72,11 @@ export class Deployment extends Resource { }); if (props.retainDeployments) { - this.resource.options.deletionPolicy = DeletionPolicy.Retain; + this.resource.options.deletionPolicy = CfnDeletionPolicy.Retain; } this.api = props.api; - this.deploymentId = Lazy.stringValue({ produce: () => this.resource.refAsString }); + this.deploymentId = Lazy.stringValue({ produce: () => this.resource.ref }); } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index 5883555d69698..306d70b76a19a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -154,12 +154,12 @@ export enum ContentHandling { /** * Converts a request payload from a base64-encoded string to a binary blob. */ - ConvertToBinary = 'CONVERT_TO_BINARY', + CONVERT_TO_BINARY = 'CONVERT_TO_BINARY', /** * Converts a request payload from a binary blob to a base64-encoded string. */ - ConvertToText = 'CONVERT_TO_TEXT' + CONVERT_TO_TEXT = 'CONVERT_TO_TEXT' } export enum IntegrationType { @@ -170,34 +170,34 @@ export enum IntegrationType { * integration. With any other AWS service action, this is known as AWS * integration. */ - Aws = 'AWS', + AWS = 'AWS', /** * For integrating the API method request with the Lambda function-invoking * action with the client request passed through as-is. This integration is * also referred to as the Lambda proxy integration */ - AwsProxy = 'AWS_PROXY', + AWS_PROXY = 'AWS_PROXY', /** * For integrating the API method request with an HTTP endpoint, including a * private HTTP endpoint within a VPC. This integration is also referred to * as the HTTP custom integration. */ - Http = 'HTTP', + HTTP = 'HTTP', /** * For integrating the API method request with an HTTP endpoint, including a * private HTTP endpoint within a VPC, with the client request passed * through as-is. This is also referred to as the HTTP proxy integration */ - HttpProxy = 'HTTP_PROXY', + HTTP_PROXY = 'HTTP_PROXY', /** * For integrating the API method request with API Gateway as a "loop-back" * endpoint without invoking any backend. */ - Mock = 'MOCK' + MOCK = 'MOCK' } export enum PassthroughBehavior { @@ -205,32 +205,32 @@ export enum PassthroughBehavior { * Passes the request body for unmapped content types through to the * integration back end without transformation. */ - WhenNoMatch = 'WHEN_NO_MATCH', + WHEN_NO_MATCH = 'WHEN_NO_MATCH', /** * Rejects unmapped content types with an HTTP 415 'Unsupported Media Type' * response */ - Never = 'NEVER', + NEVER = 'NEVER', /** * Allows pass-through when the integration has NO content types mapped to * templates. However if there is at least one content type defined, * unmapped content types will be rejected with the same 415 response. */ - WhenNoTemplates = 'WHEN_NO_TEMPLATES' + WHEN_NO_TEMPLATES = 'WHEN_NO_TEMPLATES' } export enum ConnectionType { /** * For connections through the public routable internet */ - Internet = 'INTERNET', + INTERNET = 'INTERNET', /** * For private connections between API Gateway and a network load balancer in a VPC */ - VpcLink = 'VPC_LINK' + VPC_LINK = 'VPC_LINK' } export interface IntegrationResponse { diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index 5ee538ee60dd0..98ca346586555 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -74,7 +74,7 @@ export class AwsIntegration extends Integration { constructor(props: AwsIntegrationProps) { const backend = props.subdomain ? `${props.subdomain}.${props.service}` : props.service; - const type = props.proxy ? IntegrationType.AwsProxy : IntegrationType.Aws; + const type = props.proxy ? IntegrationType.AWS_PROXY : IntegrationType.AWS; const { apiType, apiValue } = parseAwsApiCall(props.path, props.action, props.actionParameters); super({ type, diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts index d98f0b7853075..fac5bb7cc6800 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts @@ -41,7 +41,7 @@ export class HttpIntegration extends Integration { const proxy = props.proxy !== undefined ? props.proxy : true; const method = props.httpMethod || 'GET'; super({ - type: proxy ? IntegrationType.HttpProxy : IntegrationType.Http, + type: proxy ? IntegrationType.HTTP_PROXY : IntegrationType.HTTP, integrationHttpMethod: method, uri: url, options: props.options, diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/mock.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/mock.ts index 0c42a30ca111b..f8844d956b1e5 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/mock.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/mock.ts @@ -15,7 +15,7 @@ import { Integration, IntegrationOptions, IntegrationType } from '../integration export class MockIntegration extends Integration { constructor(options?: IntegrationOptions) { super({ - type: IntegrationType.Mock, + type: IntegrationType.MOCK, options }); } diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 4950b4afa8692..2f297dd7f3ac6 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -114,7 +114,7 @@ export class Method extends Resource { httpMethod: this.httpMethod, operationName: options.operationName || defaultMethodOptions.operationName, apiKeyRequired: options.apiKeyRequired || defaultMethodOptions.apiKeyRequired, - authorizationType: options.authorizationType || defaultMethodOptions.authorizationType || AuthorizationType.None, + authorizationType: options.authorizationType || defaultMethodOptions.authorizationType || AuthorizationType.NONE, authorizerId: authorizer && authorizer.authorizerId, requestParameters: options.requestParameters, integration: this.renderIntegration(props.integration), @@ -123,7 +123,7 @@ export class Method extends Resource { const resource = new CfnMethod(this, 'Resource', methodProps); - this.methodId = resource.refAsString; + this.methodId = resource.ref; props.resource.restApi._attachMethod(this); @@ -183,11 +183,11 @@ export class Method extends Resource { throw new Error(`'credentialsPassthrough' and 'credentialsRole' are mutually exclusive`); } - if (options.connectionType === ConnectionType.VpcLink && options.vpcLink === undefined) { + if (options.connectionType === ConnectionType.VPC_LINK && options.vpcLink === undefined) { throw new Error(`'connectionType' of VPC_LINK requires 'vpcLink' prop to be set`); } - if (options.connectionType === ConnectionType.Internet && options.vpcLink !== undefined) { + if (options.connectionType === ConnectionType.INTERNET && options.vpcLink !== undefined) { throw new Error(`cannot set 'vpcLink' where 'connectionType' is INTERNET`); } @@ -249,7 +249,7 @@ export enum AuthorizationType { /** * Open access. */ - None = 'NONE', + NONE = 'NONE', /** * Use AWS IAM permissions. @@ -259,10 +259,10 @@ export enum AuthorizationType { /** * Use a custom authorizer. */ - Custom = 'CUSTOM', + CUSTOM = 'CUSTOM', /** * Use an AWS Cognito user pool. */ - Cognito = 'COGNITO_USER_POOLS', + COGNITO = 'COGNITO_USER_POOLS', } diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 440fac0c1d824..c94f8b916caff 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -211,7 +211,7 @@ export class Resource extends ResourceBase { }; const resource = new CfnResource(this, 'Resource', resourceProps); - this.resourceId = resource.refAsString; + this.resourceId = resource.ref; this.restApi = props.parent.restApi; // render resource path (special case for root) diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index b946e5a753ce8..d6e7733855bd0 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,5 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); -import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/cdk'; +import { CfnOutput, Construct, IResource as IResourceBase, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { ApiKey, IApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { Deployment } from './deployment'; @@ -64,7 +64,7 @@ export interface RestApiProps extends ResourceOptions { * * @default - ID of the RestApi construct. */ - readonly restApiName?: string; + readonly restApiName?: PhysicalName; /** * Custom header parameters for the request. @@ -198,10 +198,12 @@ export class RestApi extends Resource implements IRestApi { private _latestDeployment: Deployment | undefined; constructor(scope: Construct, id: string, props: RestApiProps = { }) { - super(scope, id); + super(scope, id, { + physicalName: props.restApiName || PhysicalName.of(id), + }); const resource = new CfnRestApi(this, 'Resource', { - name: props.restApiName || id, + name: this.physicalName, description: props.description, policy: props.policy, failOnWarnings: props.failOnWarnings, @@ -213,7 +215,7 @@ export class RestApi extends Resource implements IRestApi { parameters: props.parameters, }); - this.restApiId = resource.refAsString; + this.restApiId = resource.ref; this.configureDeployment(props); @@ -361,29 +363,29 @@ export enum ApiKeySourceType { /** * To read the API key from the `X-API-Key` header of a request. */ - Header = 'HEADER', + HEADER = 'HEADER', /** * To read the API key from the `UsageIdentifierKey` from a custom authorizer. */ - Authorizer = 'AUTHORIZER', + AUTHORIZER = 'AUTHORIZER', } export enum EndpointType { /** * For an edge-optimized API and its custom domain name. */ - Edge = 'EDGE', + EDGE = 'EDGE', /** * For a regional API and its custom domain name. */ - Regional = 'REGIONAL', + REGIONAL = 'REGIONAL', /** * For a private API and its custom domain name. */ - Private = 'PRIVATE' + PRIVATE = 'PRIVATE' } class RootResource extends ResourceBase { diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index d89961bbdf2d7..05e3f454bb98f 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -1,4 +1,4 @@ -import { Construct, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Resource, Stack } from '@aws-cdk/cdk'; import { CfnStage } from './apigateway.generated'; import { Deployment } from './deployment'; import { IRestApi } from './restapi'; @@ -85,9 +85,9 @@ export interface StageProps extends StageOptions { } export enum MethodLoggingLevel { - Off = 'OFF', - Error = 'ERROR', - Info = 'INFO' + OFF = 'OFF', + ERROR = 'ERROR', + INFO = 'INFO' } export interface MethodDeploymentOptions { @@ -145,9 +145,9 @@ export interface MethodDeploymentOptions { * higher the TTL, the longer the response will be cached. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html * - * @default 300 + * @default Duration.minutes(5) */ - readonly cacheTtlSeconds?: number; + readonly cacheTtl?: Duration; /** * Indicates whether the cached responses are encrypted. @@ -197,7 +197,7 @@ export class Stage extends Resource { methodSettings, }); - this.stageName = resource.refAsString; + this.stageName = resource.ref; this.restApi = props.deployment.api; } @@ -224,7 +224,7 @@ export class Stage extends Resource { throttlingBurstLimit: props.throttlingBurstLimit, throttlingRateLimit: props.throttlingRateLimit, cachingEnabled: props.cachingEnabled, - cacheTtlSeconds: props.cacheTtlSeconds, + cacheTtl: props.cacheTtl, cacheDataEncrypted: props.cacheDataEncrypted }; @@ -256,7 +256,7 @@ export class Stage extends Resource { return { httpMethod, resourcePath, cacheDataEncrypted: options.cacheDataEncrypted, - cacheTtlInSeconds: options.cacheTtlSeconds, + cacheTtlInSeconds: options.cacheTtl && options.cacheTtl.toSeconds(), cachingEnabled: options.cachingEnabled, dataTraceEnabled: options.dataTraceEnabled, loggingLevel: options.loggingLevel, diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 7bd58584ebb6f..9ecf72f2a0b1e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -29,9 +29,9 @@ export interface ThrottleSettings { * Time period for which quota settings apply. */ export enum Period { - Day = 'DAY', - Week = 'WEEK', - Month = 'MONTH' + DAY = 'DAY', + WEEK = 'WEEK', + MONTH = 'MONTH' } /** @@ -76,7 +76,7 @@ export interface ThrottlingPerMethod { } /** - * Type of Usage Plan Key. Currently the only supported type is 'API_KEY' + * Type of Usage Plan Key. Currently the only supported type is 'ApiKey' */ export enum UsagePlanKeyType { ApiKey = 'API_KEY' @@ -165,7 +165,7 @@ export class UsagePlan extends Resource { this.apiStages.push(...(props.apiStages || [])); - this.usagePlanId = resource.refAsString; + this.usagePlanId = resource.ref; // Add ApiKey when if (props.apiKey) { diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 82394a2daa07b..b8dc3d296d232 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -1,5 +1,5 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); -import { Construct, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnVpcLink } from './apigateway.generated'; /** @@ -10,7 +10,7 @@ export interface VpcLinkProps { * The name used to label and identify the VPC link. * @default - automatically generated name */ - readonly vpcLinkName?: string; + readonly vpcLinkName?: PhysicalName; /** * The description of the VPC link. @@ -41,15 +41,18 @@ export class VpcLink extends Resource { private readonly targets = new Array(); constructor(scope: Construct, id: string, props: VpcLinkProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.vpcLinkName || + PhysicalName.of(Lazy.stringValue({ produce: () => this.node.uniqueId })), + }); const cfnResource = new CfnVpcLink(this, 'Resource', { - name: props.vpcLinkName || this.node.uniqueId, + name: this.physicalName, description: props.description, targetArns: Lazy.listValue({ produce: () => this.renderTargets() }) }); - this.vpcLinkId = cfnResource.refAsString; + this.vpcLinkId = cfnResource.ref; if (props.targets) { this.addTargets(...props.targets); diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 1d33f41476d9b..9450953fd1b6e 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -92,9 +92,17 @@ "awslint": { "exclude": [ "from-method:@aws-cdk/aws-apigateway.Resource", + "duration-prop-type:@aws-cdk/aws-apigateway.QuotaSettings.period", "from-method:@aws-cdk/aws-apigateway.ApiKey", - "ref-via-interface:@aws-cdk/aws-apigateway.ApiKeyProps.resources" + "ref-via-interface:@aws-cdk/aws-apigateway.ApiKeyProps.resources", + "props-physical-name:@aws-cdk/aws-apigateway.DeploymentProps", + "props-physical-name:@aws-cdk/aws-apigateway.MethodProps", + "props-physical-name:@aws-cdk/aws-apigateway.ProxyResourceProps", + "props-physical-name:@aws-cdk/aws-apigateway.ResourceProps", + "props-physical-name:@aws-cdk/aws-apigateway.UsagePlanProps", + "props-physical-name-type:@aws-cdk/aws-apigateway.StageProps.stageName", + "props-physical-name:@aws-cdk/aws-apigateway.LambdaRestApiProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index 91815bffd75d5..9babade508096 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -12,7 +12,7 @@ class Test extends cdk.Stack { cacheClusterEnabled: true, stageName: 'beta', description: 'beta stage', - loggingLevel: apigateway.MethodLoggingLevel.Info, + loggingLevel: apigateway.MethodLoggingLevel.INFO, dataTraceEnabled: true, methodOptions: { '/api/appliances/GET': { @@ -61,7 +61,7 @@ class Test extends cdk.Stack { throttle: { rateLimit: 5 }, quota: { limit: 10000, - period: apigateway.Period.Month + period: apigateway.Period.MONTH } }); plan.addApiStage({ diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index 8897601bcdbe0..839c438398ce3 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -112,7 +112,7 @@ export = { 'use default integration from api'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const defaultIntegration = new apigateway.Integration({ type: apigateway.IntegrationType.HttpProxy, uri: 'https://amazon.com' }); + const defaultIntegration = new apigateway.Integration({ type: apigateway.IntegrationType.HTTP_PROXY, uri: 'https://amazon.com' }); const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: false, @@ -223,7 +223,7 @@ export = { // WHEN api.root.addMethod('GET', new apigateway.Integration({ - type: apigateway.IntegrationType.AwsProxy, + type: apigateway.IntegrationType.AWS_PROXY, options: { credentialsRole: role } @@ -245,7 +245,7 @@ export = { // WHEN api.root.addMethod('GET', new apigateway.Integration({ - type: apigateway.IntegrationType.AwsProxy, + type: apigateway.IntegrationType.AWS_PROXY, options: { credentialsPassthrough: true } @@ -268,7 +268,7 @@ export = { // WHEN const integration = new apigateway.Integration({ - type: apigateway.IntegrationType.AwsProxy, + type: apigateway.IntegrationType.AWS_PROXY, options: { credentialsPassthrough: true, credentialsRole: role @@ -287,10 +287,10 @@ export = { // WHEN const integration = new apigateway.Integration({ - type: apigateway.IntegrationType.HttpProxy, + type: apigateway.IntegrationType.HTTP_PROXY, integrationHttpMethod: 'ANY', options: { - connectionType: ConnectionType.VpcLink, + connectionType: ConnectionType.VPC_LINK, } }); @@ -313,10 +313,10 @@ export = { // WHEN const integration = new apigateway.Integration({ - type: apigateway.IntegrationType.HttpProxy, + type: apigateway.IntegrationType.HTTP_PROXY, integrationHttpMethod: 'ANY', options: { - connectionType: ConnectionType.Internet, + connectionType: ConnectionType.INTERNET, vpcLink: link } }); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 146a51bb82274..c6b31a0f00402 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -141,7 +141,7 @@ export = { const api = new apigateway.RestApi(stack, 'restapi', { deploy: false, cloudWatchRole: false, - restApiName: 'my-rest-api' + restApiName: cdk.PhysicalName.of('my-rest-api'), }); api.root.addMethod('GET'); @@ -176,7 +176,7 @@ export = { const api = new apigateway.RestApi(stack, 'restapi', { deploy: false, cloudWatchRole: false, - restApiName: 'my-rest-api' + restApiName: cdk.PhysicalName.of('my-rest-api'), }); // WHEN @@ -460,7 +460,7 @@ export = { // WHEN const api = new apigateway.RestApi(stack, 'api', { - endpointTypes: [ apigateway.EndpointType.Edge, apigateway.EndpointType.Private ] + endpointTypes: [ apigateway.EndpointType.EDGE, apigateway.EndpointType.PRIVATE ] }); api.root.addMethod('GET'); @@ -547,7 +547,7 @@ export = { // CASE #2: should inherit integration from root and method options, but // "authorizationType" will be overridden to "None" instead of "IAM" child.addMethod('POST', undefined, { - authorizationType: apigateway.AuthorizationType.Cognito + authorizationType: apigateway.AuthorizationType.COGNITO }); const child2 = api.root.addResource('child2', { diff --git a/packages/@aws-cdk/aws-apigateway/test/test.stage.ts b/packages/@aws-cdk/aws-apigateway/test/test.stage.ts index 6aef209a42fc3..fa55e46b0013d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.stage.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.stage.ts @@ -78,7 +78,7 @@ export = { // WHEN new apigateway.Stage(stack, 'my-stage', { deployment, - loggingLevel: apigateway.MethodLoggingLevel.Info, + loggingLevel: apigateway.MethodLoggingLevel.INFO, throttlingRateLimit: 12 }); @@ -107,11 +107,11 @@ export = { // WHEN new apigateway.Stage(stack, 'my-stage', { deployment, - loggingLevel: apigateway.MethodLoggingLevel.Info, + loggingLevel: apigateway.MethodLoggingLevel.INFO, throttlingRateLimit: 12, methodOptions: { '/goo/bar/GET': { - loggingLevel: apigateway.MethodLoggingLevel.Error, + loggingLevel: apigateway.MethodLoggingLevel.ERROR, } } }); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts index f8e89653bf311..883f672fc999f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts @@ -90,7 +90,7 @@ export = { new apigateway.UsagePlan(stack, 'my-usage-plan', { quota: { limit: 10000, - period: apigateway.Period.Month + period: apigateway.Period.MONTH } }); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.vpc-link.ts b/packages/@aws-cdk/aws-apigateway/test/test.vpc-link.ts index bfd68d16edfff..cb50623ced091 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.vpc-link.ts @@ -16,7 +16,7 @@ export = { // WHEN new apigateway.VpcLink(stack, 'VpcLink', { - vpcLinkName: 'MyLink', + vpcLinkName: cdk.PhysicalName.of('MyLink'), targets: [nlb] }); @@ -71,4 +71,4 @@ export = { test.throws(() => app.synth(), /No targets added to vpc link/); test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index 23cd71a20e33a..23a2c23389fcb 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -120,7 +120,7 @@ export class ScalableTarget extends Resource implements IScalableTarget { serviceNamespace: props.serviceNamespace }); - this.scalableTargetId = resource.refAsString; + this.scalableTargetId = resource.ref; } /** @@ -219,40 +219,40 @@ export enum ServiceNamespace { /** * Elastic Container Service */ - Ecs = 'ecs', + ECS = 'ecs', /** * Elastic Map Reduce */ - ElasticMapReduce = 'elasticmapreduce', + ELASTIC_MAP_REDUCE = 'elasticmapreduce', /** * Elastic Compute Cloud */ - Ec2 = 'ec2', + EC2 = 'ec2', /** * App Stream */ - AppStream = 'appstream', + APPSTREAM = 'appstream', /** * Dynamo DB */ - DynamoDb = 'dynamodb', + DYNAMODB = 'dynamodb', /** * Relational Database Service */ - Rds = 'rds', + RDS = 'rds', /** * SageMaker */ - SageMaker = 'sagemaker', + SAGEMAKER = 'sagemaker', /** * Custom Resource */ - CustomResource = 'custom-resource', + CUSTOM_RESOURCE = 'custom-resource', } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts index 59c62f9a0f54b..96d1a0e8ff390 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts @@ -1,3 +1,5 @@ +import { Duration } from '@aws-cdk/cdk'; + /** * Schedule for scheduled scaling actions */ @@ -14,10 +16,15 @@ export abstract class Schedule { /** * Construct a schedule from an interval and a time unit */ - public static rate(interval: number, unit: TimeUnit): Schedule { - const unitStr = interval !== 1 ? `${unit}s` : unit; + public static rate(duration: Duration): Schedule { + if (duration.toSeconds() === 0) { + throw new Error('Duration cannot be 0'); + } - return new LiteralSchedule(`rate(${interval} ${unitStr})`); + let rate = maybeRate(duration.toDays({ integral: false }), 'day'); + if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); } + if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); } + return new LiteralSchedule(rate); } /** @@ -56,26 +63,6 @@ export abstract class Schedule { } } -/** - * What unit to interpret the rate in - */ -export enum TimeUnit { - /** - * The rate is in minutes - */ - Minute = 'minute', - - /** - * The rate is in hours - */ - Hour = 'hour', - - /** - * The rate is in days - */ - Day = 'day' -} - /** * Options to configure a cron expression * @@ -154,4 +141,19 @@ function formatISO(date?: Date) { } return num; } +} + +/** + * Return the rate if the rate is whole number + */ +function maybeRate(interval: number, singular: string) { + if (interval === 0 || !Number.isInteger(interval)) { return undefined; } + return makeRate(interval, singular); +} + +/** + * Return 'rate(${interval} ${singular}(s))` for the interval + */ +function makeRate(interval: number, singular: string) { + return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts index a77b9860ebaf2..6785a6f6d52db 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -37,7 +37,7 @@ export interface StepScalingActionProps { * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_StepScalingPolicyConfiguration.html * @default No cooldown period */ - readonly cooldownSec?: number; + readonly cooldown?: cdk.Duration; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -86,14 +86,14 @@ export class StepScalingAction extends cdk.Construct { scalingTargetId: props.scalingTarget.scalableTargetId, stepScalingPolicyConfiguration: { adjustmentType: props.adjustmentType, - cooldown: props.cooldownSec, + cooldown: props.cooldown && props.cooldown.toSeconds(), minAdjustmentMagnitude: props.minAdjustmentMagnitude, metricAggregationType: props.metricAggregationType, stepAdjustments: cdk.Lazy.anyValue({ produce: () => this.adjustments }), } as CfnScalingPolicy.StepScalingPolicyConfigurationProperty }); - this.scalingPolicyArn = resource.refAsString; + this.scalingPolicyArn = resource.ref; } /** @@ -120,7 +120,7 @@ export enum AdjustmentType { * * A positive number increases capacity, a negative number decreases capacity. */ - ChangeInCapacity = 'ChangeInCapacity', + CHANGE_IN_CAPACITY = 'ChangeInCapacity', /** * Add this percentage of the current capacity to itself. @@ -128,12 +128,12 @@ export enum AdjustmentType { * The number must be between -100 and 100; a positive number increases * capacity and a negative number decreases it. */ - PercentChangeInCapacity = 'PercentChangeInCapacity', + PERCENT_CHANGE_IN_CAPACITY = 'PercentChangeInCapacity', /** * Make the capacity equal to the exact number given. */ - ExactCapacity = 'ExactCapacity', + EXACT_CAPACITY = 'ExactCapacity', } /** @@ -143,17 +143,17 @@ export enum MetricAggregationType { /** * Average */ - Average = 'Average', + AVERAGE = 'Average', /** * Minimum */ - Minimum = 'Minimum', + MINIMUM = 'Minimum', /** * Maximum */ - Maximum = 'Maximum' + MAXIMUM = 'Maximum' } /** diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts index 6c314e036ff5f..85f2921110ebc 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -35,7 +35,7 @@ export interface BasicStepScalingPolicyProps { * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_StepScalingPolicyConfiguration.html * @default No cooldown period */ - readonly cooldownSec?: number; + readonly cooldown?: cdk.Duration; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -75,8 +75,8 @@ export class StepScalingPolicy extends cdk.Construct { throw new Error('You must supply at least 2 intervals for autoscaling'); } - const adjustmentType = props.adjustmentType || AdjustmentType.ChangeInCapacity; - const changesAreAbsolute = adjustmentType === AdjustmentType.ExactCapacity; + const adjustmentType = props.adjustmentType || AdjustmentType.CHANGE_IN_CAPACITY; + const changesAreAbsolute = adjustmentType === AdjustmentType.EXACT_CAPACITY; const intervals = normalizeIntervals(props.scalingSteps, changesAreAbsolute); const alarms = findAlarmThresholds(intervals); @@ -86,7 +86,7 @@ export class StepScalingPolicy extends cdk.Construct { this.lowerAction = new StepScalingAction(this, 'LowerPolicy', { adjustmentType, - cooldownSec: props.cooldownSec, + cooldown: props.cooldown, metricAggregationType: aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, scalingTarget: props.scalingTarget, @@ -101,10 +101,11 @@ export class StepScalingPolicy extends cdk.Construct { } this.lowerAlarm = new cloudwatch.Alarm(this, 'LowerAlarm', { + // Recommended by AutoScaling metric: props.metric, - periodSec: 60, // Recommended by AutoScaling + period: cdk.Duration.minutes(1), // Recommended by AutoScaling alarmDescription: 'Lower threshold scaling alarm', - comparisonOperator: cloudwatch.ComparisonOperator.LessThanOrEqualToThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: 1, threshold, }); @@ -116,7 +117,7 @@ export class StepScalingPolicy extends cdk.Construct { this.upperAction = new StepScalingAction(this, 'UpperPolicy', { adjustmentType, - cooldownSec: props.cooldownSec, + cooldown: props.cooldown, metricAggregationType: aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, scalingTarget: props.scalingTarget, @@ -131,10 +132,11 @@ export class StepScalingPolicy extends cdk.Construct { } this.upperAlarm = new cloudwatch.Alarm(this, 'UpperAlarm', { + // Recommended by AutoScaling metric: props.metric, - periodSec: 60, // Recommended by AutoScaling + period: cdk.Duration.minutes(1), // Recommended by AutoScaling alarmDescription: 'Upper threshold scaling alarm', - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanOrEqualToThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: 1, threshold, }); @@ -184,11 +186,11 @@ function aggregationTypeFromMetric(metric: cloudwatch.IMetric): MetricAggregatio const statistic = metric.toAlarmConfig().statistic; switch (statistic) { case 'Average': - return MetricAggregationType.Average; + return MetricAggregationType.AVERAGE; case 'Minimum': - return MetricAggregationType.Minimum; + return MetricAggregationType.MINIMUM; case 'Maximum': - return MetricAggregationType.Maximum; + return MetricAggregationType.MAXIMUM; default: throw new Error(`Cannot only scale on 'Minimum', 'Maximum', 'Average' metrics, got ${statistic}`); } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index 158f4eda1a59e..90113ea88c1d2 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -37,14 +37,14 @@ export interface BaseTargetTrackingProps { * * @default - No scale in cooldown. */ - readonly scaleInCooldownSec?: number; + readonly scaleInCooldown?: cdk.Duration; /** * Period after a scale out activity completes before another scale out activity can start. * * @default - No scale out cooldown. */ - readonly scaleOutCooldownSec?: number; + readonly scaleOutCooldown?: cdk.Duration; } /** @@ -115,13 +115,6 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { throw new Error(`Exactly one of 'customMetric' or 'predefinedMetric' must be specified.`); } - if (props.scaleInCooldownSec !== undefined && props.scaleInCooldownSec < 0) { - throw new RangeError(`scaleInCooldown cannot be negative, got: ${props.scaleInCooldownSec}`); - } - if (props.scaleOutCooldownSec !== undefined && props.scaleOutCooldownSec < 0) { - throw new RangeError(`scaleOutCooldown cannot be negative, got: ${props.scaleOutCooldownSec}`); - } - super(scope, id); const resource = new CfnScalingPolicy(this, 'Resource', { @@ -135,13 +128,13 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { predefinedMetricType: props.predefinedMetric, resourceLabel: props.resourceLabel, } : undefined, - scaleInCooldown: props.scaleInCooldownSec, - scaleOutCooldown: props.scaleOutCooldownSec, + scaleInCooldown: props.scaleInCooldown && props.scaleInCooldown.toSeconds(), + scaleOutCooldown: props.scaleOutCooldown && props.scaleOutCooldown.toSeconds(), targetValue: props.targetValue } }); - this.scalingPolicyArn = resource.refAsString; + this.scalingPolicyArn = resource.ref; } } @@ -166,15 +159,15 @@ function renderCustomMetric(metric?: cloudwatch.IMetric): CfnScalingPolicy.Custo * One of the predefined autoscaling metrics */ export enum PredefinedMetric { - DynamoDBReadCapacityUtilization = 'DynamoDBReadCapacityUtilization', - DynamoDBWriteCapacityUtilization = 'DynamoDBWriteCapacityUtilization', - ALBRequestCountPerTarget = 'ALBRequestCountPerTarget', - RDSReaderAverageCPUUtilization = 'RDSReaderAverageCPUUtilization', - RDSReaderAverageDatabaseConnections = 'RDSReaderAverageDatabaseConnections', - EC2SpotFleetRequestAverageCPUUtilization = 'EC2SpotFleetRequestAverageCPUUtilization', - EC2SpotFleetRequestAverageNetworkIn = 'EC2SpotFleetRequestAverageNetworkIn', - EC2SpotFleetRequestAverageNetworkOut = 'EC2SpotFleetRequestAverageNetworkOut', - SageMakerVariantInvocationsPerInstance = 'SageMakerVariantInvocationsPerInstance', - ECSServiceAverageCPUUtilization = 'ECSServiceAverageCPUUtilization', - ECSServiceAverageMemoryUtilization = 'ECSServiceAverageMemoryUtilization', + DYNAMODB_READ_CAPACITY_UTILIZATION = 'DynamoDBReadCapacityUtilization', + DYANMODB_WRITE_CAPACITY_UTILIZATION = 'DynamoDBWriteCapacityUtilization', + ALB_REQUEST_COUNT_PER_TARGET = 'ALBRequestCountPerTarget', + RDS_READER_AVERAGE_CPU_UTILIZATION = 'RDSReaderAverageCPUUtilization', + RDS_READER_AVERAGE_DATABASE_CONNECTIONS = 'RDSReaderAverageDatabaseConnections', + EC2_SPOT_FLEET_REQUEST_AVERAGE_CPU_UTILIZATION = 'EC2SpotFleetRequestAverageCPUUtilization', + EC2_SPOT_FLEET_REQUEST_AVERAGE_NETWORK_IN = 'EC2SpotFleetRequestAverageNetworkIn', + EC2_SPOT_FLEET_REQUEST_AVERAGE_NETWORK_OUT = 'EC2SpotFleetRequestAverageNetworkOut', + SAGEMAKER_VARIANT_INVOCATIONS_PER_INSTANCE = 'SageMakerVariantInvocationsPerInstance', + ECS_SERVICE_AVERAGE_CPU_UTILIZATION = 'ECSServiceAverageCPUUtilization', + ECS_SERVICE_AVERAGE_MEMORY_UTILIZATION = 'ECSServiceAverageMemoryUtilization', } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 9170a96686dea..968ae55e01516 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -85,5 +85,10 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-applicationautoscaling.ScalableTargetProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.cron.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.cron.ts index 7c4968267695e..9fcf4bf4ca3fa 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.cron.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.cron.ts @@ -1,3 +1,4 @@ +import { Duration } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import appscaling = require('../lib'); @@ -10,5 +11,19 @@ export = { 'test utc cron, hour and minute'(test: Test) { test.equals(appscaling.Schedule.cron({ hour: '18', minute: '24' }).expressionString, 'cron(24 18 * * ? *)'); test.done(); - } + }, + + 'rate must be whole number of minutes'(test: Test) { + test.throws(() => { + appscaling.Schedule.rate(Duration.seconds(12345)); + }, /'12345 seconds' cannot be converted into a whole number of minutes/); + test.done(); + }, + + 'rate cannot be 0'(test: Test) { + test.throws(() => { + appscaling.Schedule.rate(Duration.days(0)); + }, /Duration cannot be 0/); + test.done(); + }, }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.scalable-target.ts index c798f5d2f5d7a..b1d2c8e750a71 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.scalable-target.ts @@ -1,5 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); +import { Duration } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import appscaling = require('../lib'); import { createScalableTarget } from './util'; @@ -11,7 +12,7 @@ export = { // WHEN new appscaling.ScalableTarget(stack, 'Target', { - serviceNamespace: appscaling.ServiceNamespace.DynamoDb, + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, scalableDimension: 'test:TestCount', resourceId: 'test:this/test', minCapacity: 1, @@ -37,7 +38,7 @@ export = { // WHEN target.scaleOnSchedule('ScaleUp', { - schedule: appscaling.Schedule.rate(1, appscaling.TimeUnit.Minute), + schedule: appscaling.Schedule.rate(Duration.minutes(1)), maxCapacity: 50, minCapacity: 1, }); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.target-tracking.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.target-tracking.ts index 3d7cdff38ee2a..9b0b55162d68d 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.target-tracking.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.target-tracking.ts @@ -13,7 +13,7 @@ export = { // WHEN target.scaleToTrackMetric('Tracking', { - predefinedMetric: appscaling.PredefinedMetric.EC2SpotFleetRequestAverageCPUUtilization, + predefinedMetric: appscaling.PredefinedMetric.EC2_SPOT_FLEET_REQUEST_AVERAGE_CPU_UTILIZATION, targetValue: 30, }); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/util.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/util.ts index 9b6a20a960d54..1292cffccbdb4 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/util.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/util.ts @@ -6,7 +6,7 @@ import { ServiceNamespace } from '../lib'; export function createScalableTarget(scope: cdk.Construct) { return new appscaling.ScalableTarget(scope, 'Target', { - serviceNamespace: ServiceNamespace.DynamoDb, + serviceNamespace: ServiceNamespace.DYNAMODB, scalableDimension: 'test:TestCount', resourceId: 'test:this/test', minCapacity: 1, diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts index 90d47d423d184..76507c15fe11c 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/test/hooks.test.ts @@ -28,7 +28,7 @@ describe('given an AutoScalingGroup', () => { // WHEN asg.addLifecycleHook('Trans', { - lifecycleTransition: autoscaling.LifecycleTransition.InstanceLaunching, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, notificationTarget: new hooks.QueueHook(queue), }); @@ -43,7 +43,7 @@ describe('given an AutoScalingGroup', () => { // WHEN asg.addLifecycleHook('Trans', { - lifecycleTransition: autoscaling.LifecycleTransition.InstanceLaunching, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, notificationTarget: new hooks.TopicHook(topic), }); @@ -63,7 +63,7 @@ describe('given an AutoScalingGroup', () => { // WHEN asg.addLifecycleHook('Trans', { - lifecycleTransition: autoscaling.LifecycleTransition.InstanceLaunching, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, notificationTarget: new hooks.FunctionHook(fn), }); diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 55c723bf8393d..2ae6e6e0c4bb3 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -5,7 +5,7 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); -import { AutoScalingRollingUpdate, Construct, Fn, IResource, Lazy, Resource, Stack, Tag } from '@aws-cdk/cdk'; +import { CfnAutoScalingRollingUpdate, Construct, Duration, Fn, IResource, Lazy, Resource, Stack, Tag } from '@aws-cdk/cdk'; import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated'; import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook'; import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action'; @@ -130,16 +130,16 @@ export interface CommonAutoScalingGroupProps { * * The maximum value is 43200 (12 hours). * - * @default 300 (5 minutes) + * @default Duration.minutes(5) */ - readonly resourceSignalTimeoutSec?: number; + readonly resourceSignalTimeout?: Duration; /** * Default scaling cooldown for this AutoScalingGroup * - * @default 300 (5 minutes) + * @default Duration.minutes(5) */ - readonly cooldownSeconds?: number; + readonly cooldown?: Duration; /** * Whether instances in the Auto Scaling Group should have public @@ -175,7 +175,17 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps { /** * AMI to launch */ - readonly machineImage: ec2.IMachineImageSource; + readonly machineImage: ec2.IMachineImage; + + /** + * Specific UserData to use + * + * The UserData may still be mutated after creation. + * + * @default - A UserData object appropriate for the MachineImage's + * Operating System is created. + */ + readonly userData?: ec2.UserData; /** * An IAM role to associate with the instance profile assigned to this Auto Scaling Group. @@ -225,7 +235,7 @@ abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGrou public scaleOnCpuUtilization(id: string, props: CpuUtilizationScalingProps): TargetTrackingScalingPolicy { return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, { autoScalingGroup: this, - predefinedMetric: PredefinedMetric.ASGAverageCPUUtilization, + predefinedMetric: PredefinedMetric.ASG_AVERAGE_CPU_UTILIZATION, targetValue: props.targetUtilizationPercent, ...props }); @@ -237,7 +247,7 @@ abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGrou public scaleOnIncomingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy { return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, { autoScalingGroup: this, - predefinedMetric: PredefinedMetric.ASGAverageNetworkIn, + predefinedMetric: PredefinedMetric.ASG_AVERAGE_NETWORK_IN, targetValue: props.targetBytesPerSecond, ...props }); @@ -249,7 +259,7 @@ abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGrou public scaleOnOutgoingBytes(id: string, props: NetworkUtilizationScalingProps): TargetTrackingScalingPolicy { return new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, { autoScalingGroup: this, - predefinedMetric: PredefinedMetric.ASGAverageNetworkOut, + predefinedMetric: PredefinedMetric.ASG_AVERAGE_NETWORK_OUT, targetValue: props.targetBytesPerSecond, ...props }); @@ -270,7 +280,7 @@ abstract class AutoScalingGroupBase extends Resource implements IAutoScalingGrou const policy = new TargetTrackingScalingPolicy(this, `ScalingPolicy${id}`, { autoScalingGroup: this, - predefinedMetric: PredefinedMetric.ALBRequestCountPerTarget, + predefinedMetric: PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET, targetValue: props.targetRequestsPerSecond, resourceLabel, ...props @@ -354,7 +364,11 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements */ public readonly autoScalingGroupArn: string; - private readonly userDataLines = new Array(); + /** + * UserData for the instances + */ + public readonly userData: ec2.UserData; + private readonly autoScalingGroup: CfnAutoScalingGroup; private readonly securityGroup: ec2.ISecurityGroup; private readonly securityGroups: ec2.ISecurityGroup[] = []; @@ -364,10 +378,6 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements constructor(scope: Construct, id: string, props: AutoScalingGroupProps) { super(scope, id); - if (props.cooldownSeconds !== undefined && props.cooldownSeconds < 0) { - throw new RangeError(`cooldownSeconds cannot be negative, got: ${props.cooldownSeconds}`); - } - this.securityGroup = new ec2.SecurityGroup(this, 'InstanceSecurityGroup', { vpc: props.vpc, allowAllOutbound: props.allowAllOutbound !== false @@ -385,16 +395,17 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }); // use delayed evaluation - const machineImage = props.machineImage.getImage(this); - const userDataToken = Lazy.stringValue({ produce: () => Fn.base64((machineImage.os.createUserData(this.userDataLines))) }); + const imageConfig = props.machineImage.getImage(this); + this.userData = props.userData || imageConfig.userData || ec2.UserData.forOperatingSystem(imageConfig.osType); + const userDataToken = Lazy.stringValue({ produce: () => Fn.base64(this.userData.render()) }); const securityGroupsToken = Lazy.listValue({ produce: () => this.securityGroups.map(sg => sg.securityGroupId) }); const launchConfig = new CfnLaunchConfiguration(this, 'LaunchConfig', { - imageId: machineImage.imageId, + imageId: imageConfig.imageId, keyName: props.keyName, instanceType: props.instanceType.toString(), securityGroups: securityGroupsToken, - iamInstanceProfile: iamProfile.refAsString, + iamInstanceProfile: iamProfile.ref, userData: userDataToken, associatePublicIpAddress: props.associatePublicIpAddress, spotPrice: props.spotPrice, @@ -413,13 +424,13 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } - const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); + const { subnetIds, hasPublic } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { - cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, + cooldown: props.cooldown !== undefined ? props.cooldown.toSeconds().toString() : undefined, minSize: minCapacity.toString(), maxSize: maxCapacity.toString(), desiredCapacity: desiredCapacity.toString(), - launchConfigurationName: launchConfig.refAsString, + launchConfigurationName: launchConfig.ref, loadBalancerNames: Lazy.listValue({ produce: () => this.loadBalancerNames }, { omitEmpty: true }), targetGroupArns: Lazy.listValue({ produce: () => this.targetGroupArns }, { omitEmpty: true }), notificationConfigurations: !props.notificationsTopic ? undefined : [ @@ -436,13 +447,13 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements vpcZoneIdentifier: subnetIds }; - if (!props.vpc.isPublicSubnets(subnetIds) && props.associatePublicIpAddress) { + if (!hasPublic && props.associatePublicIpAddress) { throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.Public })"); } this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps); - this.osType = machineImage.os.type; - this.autoScalingGroupName = this.autoScalingGroup.refAsString; + this.osType = imageConfig.osType; + this.autoScalingGroupName = this.autoScalingGroup.ref; this.autoScalingGroupArn = Stack.of(this).formatArn({ service: 'autoscaling', resource: 'autoScalingGroup:*:autoScalingGroupName', @@ -491,8 +502,8 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements * Add command to the startup script of fleet instances. * The command must be in the scripting language supported by the fleet's OS (i.e. Linux/Windows). */ - public addUserData(...scriptLines: string[]) { - scriptLines.forEach(scriptLine => this.userDataLines.push(scriptLine)); + public addUserData(...commands: string[]) { + this.userData.addCommands(...commands); } /** @@ -506,7 +517,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements * Apply CloudFormation update policies for the AutoScalingGroup */ private applyUpdatePolicies(props: AutoScalingGroupProps) { - if (props.updateType === UpdateType.ReplacingUpdate) { + if (props.updateType === UpdateType.REPLACING_UPDATE) { this.autoScalingGroup.options.updatePolicy = { ...this.autoScalingGroup.options.updatePolicy, autoScalingReplacingUpdate: { @@ -527,7 +538,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements } }; } - } else if (props.updateType === UpdateType.RollingUpdate) { + } else if (props.updateType === UpdateType.ROLLING_UPDATE) { this.autoScalingGroup.options.updatePolicy = { ...this.autoScalingGroup.options.updatePolicy, autoScalingRollingUpdate: renderRollingUpdateConfig(props.rollingUpdateConfiguration) @@ -542,12 +553,12 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements }; } - if (props.resourceSignalCount !== undefined || props.resourceSignalTimeoutSec !== undefined) { + if (props.resourceSignalCount !== undefined || props.resourceSignalTimeout !== undefined) { this.autoScalingGroup.options.creationPolicy = { ...this.autoScalingGroup.options.creationPolicy, resourceSignal: { count: props.resourceSignalCount, - timeout: props.resourceSignalTimeoutSec !== undefined ? renderIsoDuration(props.resourceSignalTimeoutSec) : undefined, + timeout: props.resourceSignalTimeout && props.resourceSignalTimeout.toISOString(), } }; } @@ -561,19 +572,19 @@ export enum UpdateType { /** * Don't do anything */ - None = 'None', + NONE = 'None', /** * Replace the entire AutoScalingGroup * * Builds a new AutoScalingGroup first, then delete the old one. */ - ReplacingUpdate = 'Replace', + REPLACING_UPDATE = 'Replace', /** * Replace the instances in the AutoScalingGroup. */ - RollingUpdate = 'RollingUpdate', + ROLLING_UPDATE = 'RollingUpdate', } /** @@ -621,9 +632,9 @@ export interface RollingUpdateConfiguration { * PT#H#M#S, where each # is the number of hours, minutes, and seconds, * respectively). The maximum PauseTime is one hour (PT1H). * - * @default 300 if the waitOnResourceSignals property is true, otherwise 0 + * @default Duration.minutes(5) if the waitOnResourceSignals property is true, otherwise 0 */ - readonly pauseTimeSec?: number; + readonly pauseTime?: Duration; /** * Specifies whether the Auto Scaling group waits on signals from new instances during an update. @@ -652,62 +663,37 @@ export interface RollingUpdateConfiguration { } export enum ScalingProcess { - Launch = 'Launch', - Terminate = 'Terminate', - HealthCheck = 'HealthCheck', - ReplaceUnhealthy = 'ReplaceUnhealthy', - AZRebalance = 'AZRebalance', - AlarmNotification = 'AlarmNotification', - ScheduledActions = 'ScheduledActions', - AddToLoadBalancer = 'AddToLoadBalancer' + LAUNCH = 'Launch', + TERMINATE = 'Terminate', + HEALTH_CHECK = 'HealthCheck', + REPLACE_UNHEALTHY = 'ReplaceUnhealthy', + AZ_REBALANCE = 'AZRebalance', + ALARM_NOTIFICATION = 'AlarmNotification', + SCHEDULED_ACTIONS = 'ScheduledActions', + ADD_TO_LOAD_BALANCER = 'AddToLoadBalancer' } /** * Render the rolling update configuration into the appropriate object */ -function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): AutoScalingRollingUpdate { +function renderRollingUpdateConfig(config: RollingUpdateConfiguration = {}): CfnAutoScalingRollingUpdate { const waitOnResourceSignals = config.minSuccessfulInstancesPercent !== undefined ? true : false; - const pauseTimeSec = config.pauseTimeSec !== undefined ? config.pauseTimeSec : (waitOnResourceSignals ? 300 : 0); + const pauseTime = config.pauseTime || (waitOnResourceSignals ? Duration.minutes(5) : Duration.seconds(0)); return { maxBatchSize: config.maxBatchSize, minInstancesInService: config.minInstancesInService, minSuccessfulInstancesPercent: validatePercentage(config.minSuccessfulInstancesPercent), waitOnResourceSignals, - pauseTime: renderIsoDuration(pauseTimeSec), + pauseTime: pauseTime && pauseTime.toISOString(), suspendProcesses: config.suspendProcesses !== undefined ? config.suspendProcesses : // Recommended list of processes to suspend from here: // https://aws.amazon.com/premiumsupport/knowledge-center/auto-scaling-group-rolling-updates/ - [ScalingProcess.HealthCheck, ScalingProcess.ReplaceUnhealthy, ScalingProcess.AZRebalance, - ScalingProcess.AlarmNotification, ScalingProcess.ScheduledActions], + [ScalingProcess.HEALTH_CHECK, ScalingProcess.REPLACE_UNHEALTHY, ScalingProcess.AZ_REBALANCE, + ScalingProcess.ALARM_NOTIFICATION, ScalingProcess.SCHEDULED_ACTIONS], }; } -/** - * Render a number of seconds to a PTnX string. - */ -function renderIsoDuration(seconds: number): string { - const ret: string[] = []; - - if (seconds === 0) { - return 'PT0S'; - } - - if (seconds >= 3600) { - ret.push(`${Math.floor(seconds / 3600)}H`); - seconds %= 3600; - } - if (seconds >= 60) { - ret.push(`${Math.floor(seconds / 60)}M`); - seconds %= 60; - } - if (seconds > 0) { - ret.push(`${seconds}S`); - } - - return 'PT' + ret.join(''); -} - function validatePercentage(x?: number): number | undefined { if (x === undefined || (0 <= x && x <= 100)) { return x; } throw new Error(`Expected: a percentage 0..100, got: ${x}`); diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts index 9ab2cf235237b..e7ec5d001beca 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts @@ -1,5 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, PhysicalName, Resource } from '@aws-cdk/cdk'; import { IAutoScalingGroup } from './auto-scaling-group'; import { CfnLifecycleHook } from './autoscaling.generated'; import { ILifecycleHookTarget } from './lifecycle-hook-target'; @@ -13,7 +13,7 @@ export interface BasicLifecycleHookProps { * * @default - Automatically generated name. */ - readonly lifecycleHookName?: string; + readonly lifecycleHookName?: PhysicalName; /** * The action the Auto Scaling group takes when the lifecycle hook timeout elapses or if an unexpected failure occurs. @@ -29,7 +29,7 @@ export interface BasicLifecycleHookProps { * * @default - No heartbeat timeout. */ - readonly heartbeatTimeoutSec?: number; + readonly heartbeatTimeout?: Duration; /** * The state of the Amazon EC2 instance to which you want to attach the lifecycle hook. @@ -92,7 +92,9 @@ export class LifecycleHook extends Resource implements ILifecycleHook { public readonly lifecycleHookName: string; constructor(scope: Construct, id: string, props: LifecycleHookProps) { - super(scope, id); + super(scope, id, { + physicalName: props.lifecycleHookName, + }); this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('autoscaling.amazonaws.com') @@ -103,8 +105,8 @@ export class LifecycleHook extends Resource implements ILifecycleHook { const resource = new CfnLifecycleHook(this, 'Resource', { autoScalingGroupName: props.autoScalingGroup.autoScalingGroupName, defaultResult: props.defaultResult, - heartbeatTimeout: props.heartbeatTimeoutSec, - lifecycleHookName: props.lifecycleHookName, + heartbeatTimeout: props.heartbeatTimeout && props.heartbeatTimeout.toSeconds(), + lifecycleHookName: this.physicalName, lifecycleTransition: props.lifecycleTransition, notificationMetadata: props.notificationMetadata, notificationTargetArn: targetProps.notificationTargetArn, @@ -116,13 +118,13 @@ export class LifecycleHook extends Resource implements ILifecycleHook { // lifecycle hook. resource.node.addDependency(this.role); - this.lifecycleHookName = resource.refAsString; + this.lifecycleHookName = resource.ref; } } export enum DefaultResult { - Continue = 'CONTINUE', - Abandon = 'ABANDON', + CONTINUE = 'CONTINUE', + ABANDON = 'ABANDON', } /** @@ -132,10 +134,10 @@ export enum LifecycleTransition { /** * Execute the hook when an instance is about to be added */ - InstanceLaunching = 'autoscaling:EC2_INSTANCE_LAUNCHING', + INSTANCE_LAUNCHING = 'autoscaling:EC2_INSTANCE_LAUNCHING', /** * Execute the hook when an instance is about to be terminated */ - InstanceTerminating = 'autoscaling:EC2_INSTANCE_TERMINATING', + INSTANCE_TERMINATING = 'autoscaling:EC2_INSTANCE_TERMINATING', } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts index fd8a8215006aa..e68513c37207a 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts @@ -16,14 +16,14 @@ export interface StepScalingActionProps { * * @default The default cooldown configured on the AutoScalingGroup */ - readonly cooldownSeconds?: number; + readonly cooldown?: cdk.Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - readonly estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmup?: cdk.Duration; /** * How the adjustment numbers are interpreted @@ -73,15 +73,15 @@ export class StepScalingAction extends cdk.Construct { const resource = new CfnScalingPolicy(this, 'Resource', { policyType: 'StepScaling', autoScalingGroupName: props.autoScalingGroup.autoScalingGroupName, - cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, - estimatedInstanceWarmup: props.estimatedInstanceWarmupSeconds, + cooldown: props.cooldown && props.cooldown.toSeconds().toString(), + estimatedInstanceWarmup: props.estimatedInstanceWarmup && props.estimatedInstanceWarmup.toSeconds(), adjustmentType: props.adjustmentType, minAdjustmentMagnitude: props.minAdjustmentMagnitude, metricAggregationType: props.metricAggregationType, stepAdjustments: cdk.Lazy.anyValue({ produce: () => this.adjustments }), }); - this.scalingPolicyArn = resource.refAsString; + this.scalingPolicyArn = resource.ref; } /** @@ -108,7 +108,7 @@ export enum AdjustmentType { * * A positive number increases capacity, a negative number decreases capacity. */ - ChangeInCapacity = 'ChangeInCapacity', + CHANGE_IN_CAPACITY = 'ChangeInCapacity', /** * Add this percentage of the current capacity to itself. @@ -116,12 +116,12 @@ export enum AdjustmentType { * The number must be between -100 and 100; a positive number increases * capacity and a negative number decreases it. */ - PercentChangeInCapacity = 'PercentChangeInCapacity', + PERCENT_CHANGE_IN_CAPACITY = 'PercentChangeInCapacity', /** * Make the capacity equal to the exact number given. */ - ExactCapacity = 'ExactCapacity', + EXACT_CAPACITY = 'ExactCapacity', } /** @@ -131,17 +131,17 @@ export enum MetricAggregationType { /** * Average */ - Average = 'Average', + AVERAGE = 'Average', /** * Minimum */ - Minimum = 'Minimum', + MINIMUM = 'Minimum', /** * Maximum */ - Maximum = 'Maximum' + MAXIMUM = 'Maximum' } /** diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts index c91e1098f6cdd..fe507ef052254 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts @@ -29,14 +29,14 @@ export interface BasicStepScalingPolicyProps { * * @default Default cooldown period on your AutoScalingGroup */ - readonly cooldownSeconds?: number; + readonly cooldown?: cdk.Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - readonly estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmup?: cdk.Duration; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -76,8 +76,8 @@ export class StepScalingPolicy extends cdk.Construct { throw new Error('You must supply at least 2 intervals for autoscaling'); } - const adjustmentType = props.adjustmentType || AdjustmentType.ChangeInCapacity; - const changesAreAbsolute = adjustmentType === AdjustmentType.ExactCapacity; + const adjustmentType = props.adjustmentType || AdjustmentType.CHANGE_IN_CAPACITY; + const changesAreAbsolute = adjustmentType === AdjustmentType.EXACT_CAPACITY; const intervals = normalizeIntervals(props.scalingSteps, changesAreAbsolute); const alarms = findAlarmThresholds(intervals); @@ -87,7 +87,7 @@ export class StepScalingPolicy extends cdk.Construct { this.lowerAction = new StepScalingAction(this, 'LowerPolicy', { adjustmentType: props.adjustmentType, - cooldownSeconds: props.cooldownSeconds, + cooldown: props.cooldown, metricAggregationType: aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, autoScalingGroup: props.autoScalingGroup, @@ -102,10 +102,11 @@ export class StepScalingPolicy extends cdk.Construct { } this.lowerAlarm = new cloudwatch.Alarm(this, 'LowerAlarm', { + // Recommended by AutoScaling metric: props.metric, - periodSec: 60, // Recommended by AutoScaling + period: cdk.Duration.minutes(1), // Recommended by AutoScaling alarmDescription: 'Lower threshold scaling alarm', - comparisonOperator: cloudwatch.ComparisonOperator.LessThanOrEqualToThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: 1, threshold, }); @@ -117,7 +118,7 @@ export class StepScalingPolicy extends cdk.Construct { this.upperAction = new StepScalingAction(this, 'UpperPolicy', { adjustmentType: props.adjustmentType, - cooldownSeconds: props.cooldownSeconds, + cooldown: props.cooldown, metricAggregationType: aggregationTypeFromMetric(props.metric), minAdjustmentMagnitude: props.minAdjustmentMagnitude, autoScalingGroup: props.autoScalingGroup, @@ -134,9 +135,9 @@ export class StepScalingPolicy extends cdk.Construct { this.upperAlarm = new cloudwatch.Alarm(this, 'UpperAlarm', { // Recommended by AutoScaling metric: props.metric, - periodSec: 60, // Recommended by AutoScaling + period: cdk.Duration.minutes(1), // Recommended by AutoScaling alarmDescription: 'Upper threshold scaling alarm', - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanOrEqualToThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, evaluationPeriods: 1, threshold, }); @@ -149,11 +150,11 @@ function aggregationTypeFromMetric(metric: cloudwatch.IMetric): MetricAggregatio const statistic = metric.toAlarmConfig().statistic; switch (statistic) { case 'Average': - return MetricAggregationType.Average; + return MetricAggregationType.AVERAGE; case 'Minimum': - return MetricAggregationType.Minimum; + return MetricAggregationType.MINIMUM; case 'Maximum': - return MetricAggregationType.Maximum; + return MetricAggregationType.MAXIMUM; default: throw new Error(`Cannot only scale on 'Minimum', 'Maximum', 'Average' metrics, got ${statistic}`); } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts index 1f2ed373787b6..09688d4478899 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts @@ -29,14 +29,14 @@ export interface BaseTargetTrackingProps { * * @default - The default cooldown configured on the AutoScalingGroup. */ - readonly cooldownSeconds?: number; + readonly cooldown?: cdk.Duration; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default - Same as the cooldown. */ - readonly estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmup?: cdk.Duration; } /** @@ -113,15 +113,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { throw new Error(`Exactly one of 'customMetric' or 'predefinedMetric' must be specified.`); } - if (props.cooldownSeconds !== undefined && props.cooldownSeconds < 0) { - throw new RangeError(`cooldownSeconds cannot be negative, got: ${props.cooldownSeconds}`); - } - - if (props.estimatedInstanceWarmupSeconds !== undefined && props.estimatedInstanceWarmupSeconds < 0) { - throw new RangeError(`estimatedInstanceWarmupSeconds cannot be negative, got: ${props.estimatedInstanceWarmupSeconds}`); - } - - if (props.predefinedMetric === PredefinedMetric.ALBRequestCountPerTarget && !props.resourceLabel) { + if (props.predefinedMetric === PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET && !props.resourceLabel) { throw new Error('When tracking the ALBRequestCountPerTarget metric, the ALB identifier must be supplied in resourceLabel'); } @@ -130,8 +122,8 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { this.resource = new CfnScalingPolicy(this, 'Resource', { policyType: 'TargetTrackingScaling', autoScalingGroupName: props.autoScalingGroup.autoScalingGroupName, - cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, - estimatedInstanceWarmup: props.estimatedInstanceWarmupSeconds, + cooldown: props.cooldown && props.cooldown.toSeconds().toString(), + estimatedInstanceWarmup: props.estimatedInstanceWarmup && props.estimatedInstanceWarmup.toSeconds(), targetTrackingConfiguration: { customizedMetricSpecification: renderCustomMetric(props.customMetric), disableScaleIn: props.disableScaleIn, @@ -143,7 +135,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { } }); - this.scalingPolicyArn = this.resource.refAsString; + this.scalingPolicyArn = this.resource.ref; } } @@ -171,22 +163,22 @@ export enum PredefinedMetric { /** * Average CPU utilization of the Auto Scaling group */ - ASGAverageCPUUtilization = 'ASGAverageCPUUtilization', + ASG_AVERAGE_CPU_UTILIZATION = 'ASGAverageCPUUtilization', /** * Average number of bytes received on all network interfaces by the Auto Scaling group */ - ASGAverageNetworkIn = 'ASGAverageNetworkIn', + ASG_AVERAGE_NETWORK_IN = 'ASGAverageNetworkIn', /** * Average number of bytes sent out on all network interfaces by the Auto Scaling group */ - ASGAverageNetworkOut = 'ASGAverageNetworkOut', + ASG_AVERAGE_NETWORK_OUT = 'ASGAverageNetworkOut', /** * Number of requests completed per target in an Application Load Balancer target group * * Specify the ALB to look at in the `resourceLabel` field. */ - ALBRequestCountPerTarget = 'ALBRequestCountPerTarget', + ALB_REQUEST_COUNT_PER_TARGET = 'ALBRequestCountPerTarget', } diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index f068541fcf937..6ce3804c227e3 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -97,8 +97,10 @@ "exclude": [ "import-props-interface:@aws-cdk/aws-autoscaling.AutoScalingGroupImportProps", "resource-interface-extends-construct:@aws-cdk/aws-autoscaling.IAutoScalingGroup", - "export:@aws-cdk/aws-autoscaling.IAutoScalingGroup" + "export:@aws-cdk/aws-autoscaling.IAutoScalingGroup", + "props-physical-name:@aws-cdk/aws-autoscaling.AutoScalingGroupProps", + "props-physical-name:@aws-cdk/aws-autoscaling.ScheduledActionProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/example.images.lit.ts b/packages/@aws-cdk/aws-autoscaling/test/example.images.lit.ts index 68f7fbe6ae212..156e2745b6828 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/example.images.lit.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/example.images.lit.ts @@ -2,15 +2,15 @@ import ec2 = require("@aws-cdk/aws-ec2"); /// !show // Pick a Windows edition to use -const windows = new ec2.WindowsImage(ec2.WindowsVersion.WindowsServer2019EnglishFullBase); +const windows = new ec2.WindowsImage(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE); // Pick the right Amazon Linux edition. All arguments shown are optional // and will default to these values when omitted. const amznLinux = new ec2.AmazonLinuxImage({ - generation: ec2.AmazonLinuxGeneration.AmazonLinux, - edition: ec2.AmazonLinuxEdition.Standard, + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX, + edition: ec2.AmazonLinuxEdition.STANDARD, virtualization: ec2.AmazonLinuxVirt.HVM, - storage: ec2.AmazonLinuxStorage.GeneralPurpose, + storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, }); // For other custom (Linux) images, instantiate a `GenericLinuxImage` with diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json index 36919e49f9646..00f727cca526f 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.expected.json @@ -428,7 +428,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -467,4 +467,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.ts index d812205407785..52306e76e53b6 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.amazonlinux2.ts @@ -12,8 +12,8 @@ const vpc = new ec2.Vpc(stack, 'VPC', { new autoscaling.AutoScalingGroup(stack, 'Fleet', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), - machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AmazonLinux2 }), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), }); app.synth(); diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json index c8f20ad8aa6bd..362581f9be8c0 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.expected.json @@ -602,7 +602,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -735,4 +735,4 @@ ] } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts index 458f420512b2e..235eb3b0ceb37 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-classic-loadbalancer.ts @@ -13,7 +13,7 @@ const vpc = new ec2.Vpc(stack, 'VPC', { const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json index cf18393b3d0cf..fd90ff4aac3a1 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json @@ -449,7 +449,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -667,4 +667,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts index e9e30f9712e3e..4bb96d7a63dfe 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.ts @@ -13,7 +13,7 @@ const vpc = new ec2.Vpc(stack, 'VPC', { const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json index af6f77223f4cb..ad1777577b027 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.expected.json @@ -428,7 +428,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -502,4 +502,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.ts index fcadadbe0d545..2bf929d10d267 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.custom-scaling.ts @@ -12,8 +12,8 @@ const vpc = new ec2.Vpc(stack, 'VPC', { const asg = new autoscaling.AutoScalingGroup(stack, 'Fleet', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), - machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AmazonLinux2 }), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), }); asg.scaleOnSchedule('ScaleUpInTheMorning', { diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json index 44e040031454c..1df2089be9bd2 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.expected.json @@ -581,7 +581,7 @@ } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -623,4 +623,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.ts index 25748bd083fd0..b3c192adf81f3 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.external-role.ts @@ -13,7 +13,7 @@ class TestStack extends cdk.Stack { }); new asg.AutoScalingGroup(this, 'ASG', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.T2, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO), vpc, machineImage: new ec2.AmazonLinuxImage(), role diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.expected.json b/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.expected.json index 8dd9fdaf2d9da..9e3f6e786723d 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.expected.json +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.expected.json @@ -429,7 +429,7 @@ ], "SpotPrice": "0.20", "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -468,4 +468,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.ts b/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.ts index e57731e277472..7be3961428631 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/integ.spot-instances.ts @@ -12,8 +12,8 @@ const vpc = new ec2.Vpc(stack, 'VPC', { new autoscaling.AutoScalingGroup(stack, 'Fleet', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Micro), - machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AmazonLinux2 }), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage({ generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), spotPrice: '0.20' }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 1327627868723..b1da8df6a1964 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -13,7 +13,7 @@ export = { const vpc = mockVpc(stack); new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc }); @@ -92,7 +92,7 @@ export = { } ], "UserData": { - "Fn::Base64": "#!/bin/bash\n" + "Fn::Base64": "#!/bin/bash" } }, "DependsOn": [ @@ -137,7 +137,7 @@ export = { const vpc = mockVpc(stack); new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 0, @@ -155,6 +155,56 @@ export = { test.done(); }, + 'userdata can be overriden by image'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + const ud = ec2.UserData.forLinux(); + ud.addCommands('it me!'); + + // WHEN + const asg = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage({ + userData: ud + }), + vpc, + }); + + // THEN + test.equals(asg.userData.render(), '#!/bin/bash\nit me!'); + + test.done(); + }, + + 'userdata can be overriden at ASG directly'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + const ud1 = ec2.UserData.forLinux(); + ud1.addCommands('it me!'); + + const ud2 = ec2.UserData.forLinux(); + ud2.addCommands('no me!'); + + // WHEN + const asg = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), + machineImage: new ec2.AmazonLinuxImage({ + userData: ud1 + }), + vpc, + userData: ud2 + }); + + // THEN + test.equals(asg.userData.render(), '#!/bin/bash\nno me!'); + + test.done(); + }, + 'can specify only min capacity'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -162,7 +212,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 10 @@ -186,7 +236,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, maxCapacity: 10 @@ -210,7 +260,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, desiredCapacity: 10 @@ -232,7 +282,7 @@ export = { const vpc = mockVpc(stack); const fleet = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc }); @@ -264,10 +314,10 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, - updateType: autoscaling.UpdateType.ReplacingUpdate, + updateType: autoscaling.UpdateType.REPLACING_UPDATE, replacingUpdateMinSuccessfulInstancesPercent: 50 }); @@ -295,13 +345,13 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, - updateType: autoscaling.UpdateType.RollingUpdate, + updateType: autoscaling.UpdateType.ROLLING_UPDATE, rollingUpdateConfiguration: { minSuccessfulInstancesPercent: 50, - pauseTimeSec: 345 + pauseTime: cdk.Duration.seconds(345) } }); @@ -327,11 +377,11 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, resourceSignalCount: 5, - resourceSignalTimeoutSec: 666 + resourceSignalTimeout: cdk.Duration.seconds(666) }); // THEN @@ -354,7 +404,7 @@ export = { // WHEN const asg = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, }); @@ -380,13 +430,13 @@ export = { // WHEN const asg = new autoscaling.AutoScalingGroup(stack, 'MyFleet', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, - updateType: autoscaling.UpdateType.RollingUpdate, + updateType: autoscaling.UpdateType.ROLLING_UPDATE, rollingUpdateConfiguration: { minSuccessfulInstancesPercent: 50, - pauseTimeSec: 345 + pauseTime: cdk.Duration.seconds(345) }, }); asg.node.applyAspect(new cdk.Tag('superfood', 'acai')); @@ -422,7 +472,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyStack', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, @@ -444,14 +494,14 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyStack', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 0, maxCapacity: 0, desiredCapacity: 0, - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, associatePublicIpAddress: true, }); @@ -471,7 +521,7 @@ export = { // WHEN test.throws(() => { new autoscaling.AutoScalingGroup(stack, 'MyStack', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 0, @@ -490,7 +540,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyStack', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 0, @@ -514,7 +564,7 @@ export = { // WHEN new autoscaling.AutoScalingGroup(stack, 'MyStack', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), vpc, minCapacity: 0, @@ -544,7 +594,7 @@ export = { // WHEN const asg = new autoscaling.AutoScalingGroup(stack, 'MyASG', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), role: importedRole }); diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts b/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts index d1b56d3328c78..8d1bfedcc4ac2 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.lifecyclehooks.ts @@ -13,15 +13,15 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), }); // WHEN asg.addLifecycleHook('Transition', { notificationTarget: new FakeNotificationTarget(), - lifecycleTransition: autoscaling.LifecycleTransition.InstanceLaunching, - defaultResult: autoscaling.DefaultResult.Abandon, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING, + defaultResult: autoscaling.DefaultResult.ABANDON, }); // THEN diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts b/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts index d24151415fd9e..c2faf188ea1a3 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.scaling.ts @@ -233,8 +233,8 @@ class ASGFixture extends cdk.Construct { this.vpc = new ec2.Vpc(this, 'VPC'); this.asg = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc: this.vpc, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), }); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts index 76bb56827422c..27a2b8fef4969 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.scheduled-action.ts @@ -108,6 +108,6 @@ function makeAutoScalingGroup(scope: cdk.Construct) { vpc, instanceType: new ec2.InstanceType('t2.micro'), machineImage: new ec2.AmazonLinuxImage(), - updateType: autoscaling.UpdateType.RollingUpdate, + updateType: autoscaling.UpdateType.ROLLING_UPDATE, }); } diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js index 250afa666c31a..cd384a67d8bef 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/lib/index.js @@ -140,10 +140,10 @@ const requestCertificate = async function (requestId, domainName, subjectAlterna console.log('Waiting for validation...'); await acm.waitFor('certificateValidated', { - // Wait up to 5 minutes + // Wait up to 9 minutes and 30 seconds $waiter: { delay: 30, - maxAttempts: 10 + maxAttempts: 19 }, CertificateArn: reqCertResponse.CertificateArn }).promise(); diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json index 22425269e1277..1c75e7de20f37 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json @@ -1,6 +1,6 @@ { "name": "dns_validated_certificate_handler", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts index 0ca9bcbba7f68..9355b6fa449be 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts @@ -87,7 +87,7 @@ export class Certificate extends Resource implements ICertificate { domainValidationOptions: allDomainNames.map(domainValidationOption), }); - this.certificateArn = cert.refAsString; + this.certificateArn = cert.ref; /** * Return the domain validation options for the given domain diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index 0729a3f98bd20..876335bbca05d 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -51,7 +51,7 @@ export class DnsValidatedCertificate extends cdk.Resource implements ICertificat code: lambda.Code.asset(path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')), handler: 'index.certificateRequestHandler', runtime: lambda.Runtime.Nodejs810, - timeout: 15 * 60 // 15 minutes + timeout: cdk.Duration.minutes(15) }); requestorFunction.addToRolePolicy(new iam.PolicyStatement({ actions: ['acm:RequestCertificate', 'acm:DescribeCertificate', 'acm:DeleteCertificate'], diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index d336d1022e0a8..02ada6dca27d8 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -86,5 +86,11 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-certificatemanager.CertificateProps", + "props-physical-name:@aws-cdk/aws-certificatemanager.DnsValidatedCertificateProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts index 55a4cb1b29f75..e1437d1c13704 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts @@ -28,4 +28,13 @@ export enum CloudFormationCapabilities { * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities */ NamedIAM = 'CAPABILITY_NAMED_IAM', + + /** + * Capability to run CloudFormation macros + * + * Pass this capability if your template includes macros, for example AWS::Include or AWS::Serverless. + * + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html + */ + AutoExpand = 'CAPABILITY_AUTO_EXPAND' } diff --git a/packages/@aws-cdk/aws-cloudformation/package-lock.json b/packages/@aws-cdk/aws-cloudformation/package-lock.json index 93056873737f1..cd40533cbfa97 100644 --- a/packages/@aws-cdk/aws-cloudformation/package-lock.json +++ b/packages/@aws-cdk/aws-cloudformation/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudformation", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 629b8aa756f81..0232a60e9931b 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -101,8 +101,9 @@ "exclude": [ "construct-ctor:@aws-cdk/aws-cloudformation.PipelineCloudFormationAction.", "construct-ctor:@aws-cdk/aws-cloudformation.PipelineCloudFormationDeployAction.", - "construct-ctor-props-optional:@aws-cdk/aws-cloudformation.AwsCustomResource" + "construct-ctor-props-optional:@aws-cdk/aws-cloudformation.AwsCustomResource", + "props-physical-name:@aws-cdk/aws-cloudformation.CustomResourceProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.aws-custom-resource.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.aws-custom-resource.ts index b3eb41d3b19df..8aca2b3bf2cc6 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.aws-custom-resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.aws-custom-resource.ts @@ -2,6 +2,7 @@ import sns = require('@aws-cdk/aws-sns'); import ssm = require('@aws-cdk/aws-ssm'); import cdk = require('@aws-cdk/cdk'); +import { Token } from '@aws-cdk/cdk'; import { AwsCustomResource } from '../lib'; const app = new cdk.App(); @@ -46,8 +47,8 @@ const getParameter = new AwsCustomResource(stack, 'GetParameter', { } }); -new cdk.CfnOutput(stack, 'MessageId', { value: snsPublish.getData('MessageId') }); -new cdk.CfnOutput(stack, 'TopicArn', { value: listTopics.getData('Topics.0.TopicArn') }); -new cdk.CfnOutput(stack, 'ParameterValue', { value: getParameter.getData('Parameter.Value') }); +new cdk.CfnOutput(stack, 'MessageId', { value: Token.asString(snsPublish.getData('MessageId')) }); +new cdk.CfnOutput(stack, 'TopicArn', { value: Token.asString(listTopics.getData('Topics.0.TopicArn')) }); +new cdk.CfnOutput(stack, 'ParameterValue', { value: Token.asString(getParameter.getData('Parameter.Value')) }); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts index acc41d471a5b7..f085f70568acf 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.trivial-lambda-resource.ts @@ -27,7 +27,7 @@ class DemoResource extends cdk.Construct { // This makes the demo only work as top-level TypeScript program, but that's fine for now code: new lambda.InlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })), handler: 'index.main', - timeout: 300, + timeout: cdk.Duration.minutes(5), runtime: lambda.Runtime.Python27, })), properties: props diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts b/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts index a46121b670ab4..be3f7af39aa57 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.resource.ts @@ -204,7 +204,7 @@ class TestCustomResource extends cdk.Construct { code: new lambda.InlineCode('def hello(): pass'), runtime: lambda.Runtime.Python27, handler: 'index.hello', - timeout: 300, + timeout: cdk.Duration.minutes(5), }); new CustomResource(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 75753c2ebf3c9..1162cccb2e6d7 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -13,18 +13,18 @@ export enum HttpVersion { * The price class determines how many edge locations CloudFront will use for your distribution. */ export enum PriceClass { - PriceClass100 = "PriceClass_100", - PriceClass200 = "PriceClass_200", - PriceClassAll = "PriceClass_All" + PRICE_CLASS_100 = "PriceClass_100", + PRICE_CLASS_200 = "PriceClass_200", + PRICE_CLASS_ALL = "PriceClass_All" } /** * How HTTPs should be handled with your distribution. */ export enum ViewerProtocolPolicy { - HTTPSOnly = "https-only", - RedirectToHTTPS = "redirect-to-https", - AllowAll = "allow-all" + HTTPS_ONLY = "https-only", + REDIRECT_TO_HTTPS = "redirect-to-https", + ALLOW_ALL = "allow-all" } /** @@ -95,11 +95,11 @@ export enum SSLMethod { * CloudFront serves your objects only to browsers or devices that support at least the SSL version that you specify. */ export enum SecurityPolicyProtocol { - SSLv3 = "SSLv3", - TLSv1 = "TLSv1", - TLSv1_2016 = "TLSv1_2016", - TLSv1_1_2016 = "TLSv1.1_2016", - TLSv1_2_2018 = "TLSv1.2_2018" + SSL_V3 = "SSLv3", + TLS_V1 = "TLSv1", + TLS_V1_2016 = "TLSv1_2016", + TLS_V1_1_2016 = "TLSv1.1_2016", + TLS_V1_2_2018 = "TLSv1.2_2018" } /** @@ -194,9 +194,9 @@ export interface CustomOriginConfig { /** * The keep alive timeout when making calls in seconds. * - * @default 5 + * @default Duration.seconds(5) */ - readonly originKeepaliveTimeoutSeconds?: number, + readonly originKeepaliveTimeout?: cdk.Duration, /** * The protocol (http or https) policy to use when interacting with the origin. @@ -208,9 +208,9 @@ export interface CustomOriginConfig { /** * The read timeout when calling the origin in seconds * - * @default 30 + * @default Duration.seconds(30) */ - readonly originReadTimeoutSeconds?: number + readonly originReadTimeout?: cdk.Duration /** * The SSL versions to use when interacting with the origin. @@ -222,16 +222,16 @@ export interface CustomOriginConfig { } export enum OriginSslPolicy { - SSLv3 = "SSLv3", - TLSv1 = "TLSv1", - TLSv1_1 = "TLSv1.1", - TLSv1_2 = "TLSv1.2", + SSL_V3 = "SSLv3", + TLS_V1 = "TLSv1", + TLS_V1_1 = "TLSv1.1", + TLS_V1_2 = "TLSv1.2", } export enum OriginProtocolPolicy { - HttpOnly = "http-only", - MatchViewer = "match-viewer", - HttpsOnly = "https-only", + HTTP_ONLY = "http-only", + MATCH_VIEWER = "match-viewer", + HTTPS_ONLY = "https-only", } export interface S3OriginConfig { @@ -300,7 +300,7 @@ export interface Behavior { * @default 86400 (1 day) * */ - readonly defaultTtlSeconds?: number; + readonly defaultTtl?: cdk.Duration; /** * The method this CloudFront distribution responds do. @@ -335,15 +335,15 @@ export interface Behavior { * The minimum amount of time that you want objects to stay in the cache * before CloudFront queries your origin. */ - readonly minTtlSeconds?: number; + readonly minTtl?: cdk.Duration; /** * The max amount of time you want objects to stay in the cache * before CloudFront queries your origin. * - * @default 31536000 (one year) + * @default Duration.seconds(31536000) (one year) */ - readonly maxTtlSeconds?: number; + readonly maxTtl?: cdk.Duration; /** * Declares associated lambda@edge functions for this distribution behaviour. @@ -373,39 +373,20 @@ export enum LambdaEdgeEventType { * The origin-request specifies the request to the * origin location (e.g. S3) */ - OriginRequest = "origin-request", + ORIGIN_REQUEST = "origin-request", /** * The origin-response specifies the response from the * origin location (e.g. S3) */ - OriginResponse = "origin-response", + ORIGIN_RESPONSE = "origin-response", /** * The viewer-request specifies the incoming request */ - ViewerRequest = "viewer-request", + VIEWER_REQUEST = "viewer-request", /** * The viewer-response specifies the outgoing reponse */ - ViewerResponse = "viewer-response", -} - -export interface ErrorConfiguration { - /** - * The error code matched from the origin - */ - readonly originErrorCode: number; - /** - * The error code that is sent to the caller. - */ - readonly respondWithErrorCode: number; - /** - * The path to service instead - */ - readonly respondWithPage: string; - /** - * How long before this error is retried. - */ - readonly cacheTtl?: number; + VIEWER_RESPONSE = "viewer-response", } export interface CloudFrontWebDistributionProps { @@ -563,10 +544,10 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib */ private readonly VALID_SSL_PROTOCOLS: { [key: string]: string[] } = { "sni-only": [ - SecurityPolicyProtocol.TLSv1, SecurityPolicyProtocol.TLSv1_1_2016, - SecurityPolicyProtocol.TLSv1_2016, SecurityPolicyProtocol.TLSv1_2_2018 + SecurityPolicyProtocol.TLS_V1, SecurityPolicyProtocol.TLS_V1_1_2016, + SecurityPolicyProtocol.TLS_V1_2016, SecurityPolicyProtocol.TLS_V1_2_2018 ], - "vip": [SecurityPolicyProtocol.SSLv3, SecurityPolicyProtocol.TLSv1], + "vip": [SecurityPolicyProtocol.SSL_V3, SecurityPolicyProtocol.TLS_V1], }; constructor(scope: cdk.Construct, id: string, props: CloudFrontWebDistributionProps) { @@ -577,7 +558,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib enabled: true, defaultRootObject: props.defaultRootObject !== undefined ? props.defaultRootObject : "index.html", httpVersion: props.httpVersion || HttpVersion.HTTP2, - priceClass: props.priceClass || PriceClass.PriceClass100, + priceClass: props.priceClass || PriceClass.PRICE_CLASS_100, ipv6Enabled: (props.enableIpV6 !== undefined) ? props.enableIpV6 : true, // tslint:disable-next-line:max-line-length customErrorResponses: props.errorConfigurations, // TODO: validation : https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-customerrorresponse.html#cfn-cloudfront-distribution-customerrorresponse-errorcachingminttl @@ -625,10 +606,12 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib ? { httpPort: originConfig.customOriginSource.httpPort || 80, httpsPort: originConfig.customOriginSource.httpsPort || 443, - originKeepaliveTimeout: originConfig.customOriginSource.originKeepaliveTimeoutSeconds || 5, - originReadTimeout: originConfig.customOriginSource.originReadTimeoutSeconds || 30, - originProtocolPolicy: originConfig.customOriginSource.originProtocolPolicy || OriginProtocolPolicy.HttpsOnly, - originSslProtocols: originConfig.customOriginSource.allowedOriginSSLVersions || [OriginSslPolicy.TLSv1_2] + originKeepaliveTimeout: originConfig.customOriginSource.originKeepaliveTimeout + && originConfig.customOriginSource.originKeepaliveTimeout.toSeconds() || 5, + originReadTimeout: originConfig.customOriginSource.originReadTimeout + && originConfig.customOriginSource.originReadTimeout.toSeconds() || 30, + originProtocolPolicy: originConfig.customOriginSource.originProtocolPolicy || OriginProtocolPolicy.HTTPS_ONLY, + originSslProtocols: originConfig.customOriginSource.allowedOriginSSLVersions || [OriginSslPolicy.TLS_V1_2] } : undefined }; @@ -715,7 +698,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig }); this.domainName = distribution.attrDomainName; - this.distributionId = distribution.refAsString; + this.distributionId = distribution.ref; } private toBehavior(input: BehaviorWithOrigin, protoPolicy?: ViewerProtocolPolicy) { @@ -723,13 +706,13 @@ export class CloudFrontWebDistribution extends cdk.Construct implements IDistrib allowedMethods: this.METHOD_LOOKUP_MAP[input.allowedMethods || CloudFrontAllowedMethods.GET_HEAD], cachedMethods: this.METHOD_LOOKUP_MAP[input.cachedMethods || CloudFrontAllowedCachedMethods.GET_HEAD], compress: input.compress, - defaultTtl: input.defaultTtlSeconds, + defaultTtl: input.defaultTtl && input.defaultTtl.toSeconds(), forwardedValues: input.forwardedValues || { queryString: false, cookies: { forward: "none" } }, - maxTtl: input.maxTtlSeconds, - minTtl: input.minTtlSeconds, + maxTtl: input.maxTtl && input.maxTtl.toSeconds(), + minTtl: input.minTtl && input.minTtl.toSeconds(), trustedSigners: input.trustedSigners, targetOriginId: input.targetOriginId, - viewerProtocolPolicy: protoPolicy || ViewerProtocolPolicy.RedirectToHTTPS, + viewerProtocolPolicy: protoPolicy || ViewerProtocolPolicy.REDIRECT_TO_HTTPS, }; if (!input.isDefaultBehavior) { toReturn = Object.assign(toReturn, { pathPattern: input.pathPattern }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts index 231e42ffab952..70f7fcd46f811 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-lambda-association.ts @@ -28,11 +28,11 @@ new cloudfront.CloudFrontWebDistribution(stack, 'MyDistribution', { s3BucketSource: sourceBucket }, behaviors : [ {isDefaultBehavior: true, lambdaFunctionAssociations: [{ - eventType: cloudfront.LambdaEdgeEventType.OriginRequest, + eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, lambdaFunction: lambdaVersion }]}] } ] }); -app.run(); +app.synth(); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.ts index 0dd1f6b89a1ea..19612e6d83a6e 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3.ts @@ -17,7 +17,7 @@ const dist = new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { behaviors: [{ isDefaultBehavior: true }], s3OriginSource: { s3BucketSource: bucket, - originAccessIdentityId: oai.refAsString, + originAccessIdentityId: oai.ref, }, }] }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts index 7857fe03c6cb4..36eb03784c4f6 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-security-policy.ts @@ -26,7 +26,7 @@ new cloudfront.CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { acmCertRef: 'testACM', names: ['test.test.com'], sslMethod: cloudfront.SSLMethod.SNI, - securityPolicy: cloudfront.SecurityPolicyProtocol.TLSv1 + securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1 } }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts b/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts index 37fa731d04e03..78d3f1c5fa145 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts @@ -252,7 +252,7 @@ export = { const sourceBucket = new s3.Bucket(stack, 'Bucket'); new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { - viewerProtocolPolicy: ViewerProtocolPolicy.AllowAll, + viewerProtocolPolicy: ViewerProtocolPolicy.ALLOW_ALL, originConfigs: [ { s3OriginSource: { diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts index c1c5345c45089..7a45c715283f7 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts @@ -3,7 +3,7 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import logs = require('@aws-cdk/aws-logs'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnTrail } from './cloudtrail.generated'; // AWS::CloudTrail CloudFormation Resources: @@ -67,7 +67,7 @@ export interface TrailProps { * * @default logs.RetentionDays.OneYear */ - readonly cloudWatchLogsRetentionTimeDays?: logs.RetentionDays; + readonly cloudWatchLogsRetention?: logs.RetentionDays; /** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs. * @@ -86,7 +86,7 @@ export interface TrailProps { * * @default - AWS CloudFormation generated name. */ - readonly trailName?: string; + readonly trailName?: PhysicalName; /** An Amazon S3 object key prefix that precedes the name of all log files. * @@ -96,9 +96,9 @@ export interface TrailProps { } export enum ReadWriteType { - ReadOnly = "ReadOnly", - WriteOnly = "WriteOnly", - All = "All" + READ_ONLY = "ReadOnly", + WRITE_ONLY = "WriteOnly", + ALL = "All" } /** @@ -125,9 +125,11 @@ export class Trail extends Resource { private eventSelectors: EventSelector[] = []; constructor(scope: Construct, id: string, props: TrailProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.trailName, + }); - const s3bucket = new s3.Bucket(this, 'S3', {encryption: s3.BucketEncryption.Unencrypted}); + const s3bucket = new s3.Bucket(this, 'S3', {encryption: s3.BucketEncryption.UNENCRYPTED}); const cloudTrailPrincipal = new iam.ServicePrincipal("cloudtrail.amazonaws.com"); s3bucket.addToResourcePolicy(new iam.PolicyStatement({ @@ -149,7 +151,7 @@ export class Trail extends Resource { let logsRole: iam.IRole | undefined; if (props.sendToCloudWatchLogs) { logGroup = new logs.CfnLogGroup(this, "LogGroup", { - retentionInDays: props.cloudWatchLogsRetentionTimeDays || logs.RetentionDays.OneYear + retentionInDays: props.cloudWatchLogsRetention || logs.RetentionDays.ONE_YEAR }); logsRole = new iam.Role(this, 'LogsRole', { assumedBy: cloudTrailPrincipal }); @@ -173,7 +175,7 @@ export class Trail extends Resource { enableLogFileValidation: props.enableFileValidation == null ? true : props.enableFileValidation, isMultiRegionTrail: props.isMultiRegionTrail == null ? true : props.isMultiRegionTrail, includeGlobalServiceEvents: props.includeGlobalServiceEvents == null ? true : props.includeGlobalServiceEvents, - trailName: props.trailName, + trailName: this.physicalName, kmsKeyId: props.kmsKey && props.kmsKey.keyArn, s3BucketName: s3bucket.bucketName, s3KeyPrefix: props.s3KeyPrefix, @@ -183,7 +185,16 @@ export class Trail extends Resource { eventSelectors: this.eventSelectors }); - this.trailArn = trail.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: trail.attrArn, + name: trail.trailName || '', + arnComponents: { + service: 'cloudtrail', + resource: 'trail', + resourceName: this.physicalName, + }, + }); + this.trailArn = resourceIdentifiers.arn; this.trailSnsTopicArn = trail.attrSnsTopicArn; const s3BucketPolicy = s3bucket.node.findChild("Policy").node.findChild("Resource") as s3.CfnBucketPolicy; diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index e921a2c6c554d..1003530de1d4a 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -92,4 +92,4 @@ "node": ">= 8.10.0" }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts index ade48de2167f9..ab8e8f2f06f5b 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts @@ -106,7 +106,7 @@ export = { const stack = getTestStack(); new Trail(stack, 'MyAmazingCloudTrail', { sendToCloudWatchLogs: true, - cloudWatchLogsRetentionTimeDays: RetentionDays.OneWeek + cloudWatchLogsRetention: RetentionDays.ONE_WEEK }); expect(stack).to(haveResource("AWS::CloudTrail::Trail")); @@ -153,7 +153,7 @@ export = { const stack = getTestStack(); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); - cloudTrail.addS3EventSelector(["arn:aws:s3:::"], { includeManagementEvents: false, readWriteType: ReadWriteType.ReadOnly }); + cloudTrail.addS3EventSelector(["arn:aws:s3:::"], { includeManagementEvents: false, readWriteType: ReadWriteType.READ_ONLY }); expect(stack).to(haveResource("AWS::CloudTrail::Trail")); expect(stack).to(haveResource("AWS::S3::Bucket")); @@ -178,7 +178,7 @@ export = { 'with management event'(test: Test) { const stack = getTestStack(); - new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WriteOnly }); + new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WRITE_ONLY }); const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); @@ -194,7 +194,7 @@ export = { 'add an event rule'(test: Test) { // GIVEN const stack = getTestStack(); - const trail = new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WriteOnly }); + const trail = new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WRITE_ONLY }); // WHEN trail.onCloudTrailEvent('DoEvents', { diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/test/appscaling.test.ts b/packages/@aws-cdk/aws-cloudwatch-actions/test/appscaling.test.ts index 4e0c0a510c29b..28c50c1792825 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/test/appscaling.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/test/appscaling.test.ts @@ -12,7 +12,7 @@ test('can use topic as alarm action', () => { maxCapacity: 100, resourceId: 'asdf', scalableDimension: 'height', - serviceNamespace: appscaling.ServiceNamespace.CustomResource, + serviceNamespace: appscaling.ServiceNamespace.CUSTOM_RESOURCE, }); const action = new appscaling.StepScalingAction(stack, 'Action', { scalingTarget, diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index b065867a8408e..57333f16f85bd 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -35,10 +35,10 @@ export interface AlarmProps extends CreateAlarmOptions { * Comparison operator for evaluating alarms */ export enum ComparisonOperator { - GreaterThanOrEqualToThreshold = 'GreaterThanOrEqualToThreshold', - GreaterThanThreshold = 'GreaterThanThreshold', - LessThanThreshold = 'LessThanThreshold', - LessThanOrEqualToThreshold = 'LessThanOrEqualToThreshold', + GREATER_THAN_OR_EQUAL_TO_THRESHOLD = 'GreaterThanOrEqualToThreshold', + GREATER_THAN_THRESHOLD = 'GreaterThanThreshold', + LESS_THAN_THRESHOLD = 'LessThanThreshold', + LESS_THAN_OR_EQUAL_TO_THRESHOLD = 'LessThanOrEqualToThreshold', } const OPERATOR_SYMBOLS: {[key: string]: string} = { @@ -55,22 +55,22 @@ export enum TreatMissingData { /** * Missing data points are treated as breaching the threshold */ - Breaching = 'breaching', + BREACHING = 'breaching', /** * Missing data points are treated as being within the threshold */ - NotBreaching = 'notBreaching', + NOT_BREACHING = 'notBreaching', /** * The current alarm state is maintained */ - Ignore = 'ignore', + IGNORE = 'ignore', /** * The alarm does not consider missing data points when evaluating whether to change state */ - Missing = 'missing' + MISSING = 'missing' } /** @@ -115,16 +115,18 @@ export class Alarm extends Resource implements IAlarm { private readonly annotation: HorizontalAnnotation; constructor(scope: Construct, id: string, props: AlarmProps) { - super(scope, id); + super(scope, id, { + physicalName: props.alarmName, + }); - const comparisonOperator = props.comparisonOperator || ComparisonOperator.GreaterThanOrEqualToThreshold; + const comparisonOperator = props.comparisonOperator || ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD; const config = props.metric.toAlarmConfig(); const alarm = new CfnAlarm(this, 'Resource', { // Meta alarmDescription: props.alarmDescription, - alarmName: props.alarmName, + alarmName: this.physicalName, // Evaluation comparisonOperator, @@ -144,13 +146,24 @@ export class Alarm extends Resource implements IAlarm { ...dropUndef(config), ...dropUndef({ // Alarm overrides - period: props.periodSec, + period: props.period && props.period.toSeconds(), statistic: props.statistic && normalizeStatistic(props.statistic), }) }); - this.alarmArn = alarm.attrArn; - this.alarmName = alarm.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: alarm.attrArn, + name: alarm.ref, + arnComponents: { + service: 'cloudwatch', + resource: 'alarm', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.alarmArn = resourceIdentifiers.arn; + this.alarmName = resourceIdentifiers.name; + this.metric = props.metric; this.annotation = { // tslint:disable-next-line:max-line-length @@ -239,4 +252,4 @@ function dropUndef(x: T): T { } } return ret; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 45bb76981c49f..fcc6c41fee395 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -1,11 +1,11 @@ -import { Construct, Lazy, Resource, Stack } from "@aws-cdk/cdk"; +import { Construct, Lazy, PhysicalName, Resource, Stack } from "@aws-cdk/cdk"; import { CfnDashboard } from './cloudwatch.generated'; import { Column, Row } from "./layout"; import { IWidget } from "./widget"; export enum PeriodOverride { - Auto = 'auto', - Inherit = 'inherit', + AUTO = 'auto', + INHERIT = 'inherit', } export interface DashboardProps { @@ -14,7 +14,7 @@ export interface DashboardProps { * * @default Automatically generated name */ - readonly dashboardName?: string; + readonly dashboardName?: PhysicalName; /** * The start of the time range to use for each widget on the dashboard. @@ -63,10 +63,12 @@ export class Dashboard extends Resource { private readonly rows: IWidget[] = []; constructor(scope: Construct, id: string, props: DashboardProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.dashboardName, + }); new CfnDashboard(this, 'Resource', { - dashboardName: props.dashboardName, + dashboardName: this.physicalName, dashboardBody: Lazy.stringValue({ produce: () => { const column = new Column(...this.rows); column.position(0, 0); diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index aea30abd66927..0c30441ea4865 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -272,17 +272,17 @@ export enum Shading { /** * Don't add shading */ - None = 'none', + NONE = 'none', /** * Add shading above the annotation */ - Above = 'above', + ABOVE = 'above', /** * Add shading below the annotation */ - Below = 'below' + BELOW = 'below' } function mapAnnotation(yAxis: string): ((x: HorizontalAnnotation) => any) { diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts index d813a7dfbe8a9..5a3d90895ecfe 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts @@ -33,44 +33,44 @@ export interface Dimension { * Statistic to use over the aggregation period */ export enum Statistic { - SampleCount = 'SampleCount', - Average = 'Average', - Sum = 'Sum', - Minimum = 'Minimum', - Maximum = 'Maximum', + SAMPLE_COUNT = 'SampleCount', + AVERAGE = 'Average', + SUM = 'Sum', + MINIMUM = 'Minimum', + MAXIMUM = 'Maximum', } /** * Unit for metric */ export enum Unit { - Seconds = 'Seconds', - Microseconds = 'Microseconds', - Milliseconds = 'Milliseconds', - Bytes_ = 'Bytes', - Kilobytes = 'Kilobytes', - Megabytes = 'Megabytes', - Gigabytes = 'Gigabytes', - Terabytes = 'Terabytes', - Bits = 'Bits', - Kilobits = 'Kilobits', - Megabits = 'Megabits', - Gigabits = 'Gigabits', - Terabits = 'Terabits', - Percent = 'Percent', - Count = 'Count', - BytesPerSecond = 'Bytes/Second', - KilobytesPerSecond = 'Kilobytes/Second', - MegabytesPerSecond = 'Megabytes/Second', - GigabytesPerSecond = 'Gigabytes/Second', - TerabytesPerSecond = 'Terabytes/Second', - BitsPerSecond = 'Bits/Second', - KilobitsPerSecond = 'Kilobits/Second', - MegabitsPerSecond = 'Megabits/Second', - GigabitsPerSecond = 'Gigabits/Second', - TerabitsPerSecond = 'Terabits/Second', - CountPerSecond = 'Count/Second', - None = 'None' + SECONDS = 'Seconds', + MICROSECONDS = 'Microseconds', + MILLISECONDS = 'Milliseconds', + BYTES = 'Bytes', + KILOBYTES = 'Kilobytes', + MEGABYTES = 'Megabytes', + GIGABYTES = 'Gigabytes', + TERABYTES = 'Terabytes', + BITS = 'Bits', + KILOBITS = 'Kilobits', + MEGABITS = 'Megabits', + GIGABITS = 'Gigabits', + TERABITS = 'Terabits', + PERCENT = 'Percent', + COUNT = 'Count', + BYTES_PER_SECOND = 'Bytes/Second', + KILOBYTES_PER_SECOND = 'Kilobytes/Second', + MEGABYTES_PER_SECOND = 'Megabytes/Second', + GIGABYTES_PER_SECOND = 'Gigabytes/Second', + TERABYTES_PER_SECOND = 'Terabytes/Second', + BITS_PER_SECOND = 'Bits/Second', + KILOBITS_PER_SECOND = 'Kilobits/Second', + MEGABITS_PER_SECOND = 'Megabits/Second', + GIGABITS_PER_SECOND = 'Gigabits/Second', + TERABITS_PER_SECOND = 'Terabits/Second', + COUNT_PER_SECOND = 'Count/Second', + NONE = 'None' } /** diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index 2c0384ddf9d16..6d0cc5f9a26fd 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -13,11 +13,9 @@ export interface CommonMetricOptions { /** * The period over which the specified statistic is applied. * - * Specify time in seconds, in multiples of 60. - * - * @default 300 + * @default Duration.minutes(5) */ - readonly periodSec?: number; + readonly period?: cdk.Duration; /** * What function to use for aggregating. @@ -110,23 +108,22 @@ export class Metric implements IMetric { public readonly dimensions?: DimensionHash; public readonly namespace: string; public readonly metricName: string; - public readonly periodSec: number; + public readonly period: cdk.Duration; public readonly statistic: string; public readonly unit?: Unit; public readonly label?: string; public readonly color?: string; constructor(props: MetricProps) { - if (props.periodSec !== undefined - && props.periodSec !== 1 && props.periodSec !== 5 && props.periodSec !== 10 && props.periodSec !== 30 - && props.periodSec % 60 !== 0) { - throw new Error("'periodSec' must be 1, 5, 10, 30, or a multiple of 60"); + this.period = props.period || cdk.Duration.minutes(5); + const periodSec = this.period.toSeconds(); + if (periodSec !== 1 && periodSec !== 5 && periodSec !== 10 && periodSec !== 30 && periodSec % 60 !== 0) { + throw new Error(`'period' must be 1, 5, 10, 30, or a multiple of 60 seconds, received ${props.period}`); } this.dimensions = props.dimensions; this.namespace = props.namespace; this.metricName = props.metricName; - this.periodSec = props.periodSec !== undefined ? props.periodSec : 300; // Try parsing, this will throw if it's not a valid stat this.statistic = normalizeStatistic(props.statistic || "Average"); this.label = props.label; @@ -146,7 +143,7 @@ export class Metric implements IMetric { dimensions: ifUndefined(props.dimensions, this.dimensions), namespace: this.namespace, metricName: this.metricName, - periodSec: ifUndefined(props.periodSec, this.periodSec), + period: ifUndefined(props.period, this.period), statistic: ifUndefined(props.statistic, this.statistic), unit: ifUndefined(props.unit, this.unit), label: ifUndefined(props.label, this.label), @@ -164,7 +161,7 @@ export class Metric implements IMetric { return new Alarm(scope, id, { metric: this.with({ statistic: props.statistic, - periodSec: props.periodSec, + period: props.period, }), alarmName: props.alarmName, alarmDescription: props.alarmDescription, @@ -186,7 +183,7 @@ export class Metric implements IMetric { dimensions: dims.length > 0 ? dims : undefined, namespace: this.namespace, metricName: this.metricName, - period: this.periodSec, + period: this.period.toSeconds(), statistic: stat.type === 'simple' ? stat.statistic : undefined, extendedStatistic: stat.type === 'percentile' ? 'p' + stat.percentile : undefined, unit: this.unit @@ -198,7 +195,7 @@ export class Metric implements IMetric { dimensions: this.dimensionsAsList(), namespace: this.namespace, metricName: this.metricName, - period: this.periodSec, + period: this.period.toSeconds(), statistic: this.statistic, unit: this.unit, color: this.color, @@ -233,11 +230,9 @@ export interface CreateAlarmOptions { /** * The period over which the specified statistic is applied. * - * Specify time in seconds, in multiples of 60. - * - * @default 300 + * @default Duration.minutes(5) */ - readonly periodSec?: number; + readonly period?: cdk.Duration; /** * What function to use for aggregating. @@ -260,7 +255,7 @@ export interface CreateAlarmOptions { * * @default Automatically generated name */ - readonly alarmName?: string; + readonly alarmName?: cdk.PhysicalName; /** * Description for the alarm diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/util.statistic.ts b/packages/@aws-cdk/aws-cloudwatch/lib/util.statistic.ts index 6f6c590287fdf..ae6f8f7e43096 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/util.statistic.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/util.statistic.ts @@ -17,15 +17,15 @@ export function parseStatistic(stat: string): SimpleStatistic | PercentileStatis // Simple statistics const statMap: {[k: string]: Statistic} = { - average: Statistic.Average, - avg: Statistic.Average, - minimum: Statistic.Minimum, - min: Statistic.Minimum, - maximum: Statistic.Maximum, - max: Statistic.Maximum, - samplecount: Statistic.SampleCount, - n: Statistic.SampleCount, - sum: Statistic.Sum, + average: Statistic.AVERAGE, + avg: Statistic.AVERAGE, + minimum: Statistic.MINIMUM, + min: Statistic.MINIMUM, + maximum: Statistic.MAXIMUM, + max: Statistic.MAXIMUM, + samplecount: Statistic.SAMPLE_COUNT, + n: Statistic.SAMPLE_COUNT, + sum: Statistic.SUM, }; if (lowerStat in statMap) { diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index 289cdbfed58b8..f609d89253826 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -78,8 +78,14 @@ "@aws-cdk/aws-iam": "^0.35.0", "@aws-cdk/cdk": "^0.35.0" }, + "awslint": { + "exclude": [ + "duration-prop-type:@aws-cdk/aws-cloudwatch.MetricAlarmConfig.period", + "duration-prop-type:@aws-cdk/aws-cloudwatch.MetricGraphConfig.period" + ] + }, "engines": { "node": ">= 8.10.0" }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts index a1dbacc5af0f9..d516f7004031a 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/integ.alarm-and-dashboard.ts @@ -27,10 +27,10 @@ const alarm = metric.createAlarm(stack, 'Alarm', { }); const dashboard = new cloudwatch.Dashboard(stack, 'Dash', { - dashboardName: 'MyCustomDashboardName', + dashboardName: cdk.PhysicalName.of('MyCustomDashboardName'), start: '-9H', end: '2018-12-17T06:00:00.000Z', - periodOverride: PeriodOverride.Inherit + periodOverride: PeriodOverride.INHERIT }); dashboard.addWidgets( new cloudwatch.TextWidget({ markdown: '# This is my dashboard' }), diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts index dfa9f4d51eaf7..e46b314eddd6a 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.alarm.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Alarm, IAlarm, IAlarmAction, Metric } from '../lib'; @@ -41,7 +41,7 @@ export = { // WHEN new Alarm(stack, 'Alarm', { metric: testMetric, - periodSec: 600, + period: Duration.minutes(10), threshold: 1000, evaluationPeriods: 3, }); @@ -147,7 +147,7 @@ export = { threshold: 1000, evaluationPeriods: 2, statistic: 'min', - periodSec: 10, + period: Duration.seconds(10), }); // THEN diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts index 73fc6fa55288e..fe51772d402b8 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.dashboard.ts @@ -1,5 +1,5 @@ import { expect, haveResource, isSuperObject } from '@aws-cdk/assert'; -import { App, Stack } from '@aws-cdk/cdk'; +import { App, PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Dashboard, GraphWidget, PeriodOverride, TextWidget } from '../lib'; @@ -99,7 +99,7 @@ export = { { start: '-9H', end: '2018-12-17T06:00:00.000Z', - periodOverride: PeriodOverride.Inherit + periodOverride: PeriodOverride.INHERIT }); // WHEN @@ -127,7 +127,7 @@ export = { // WHEN new Dashboard(stack, 'MyDashboard', { - dashboardName: 'MyCustomDashboardName' + dashboardName: PhysicalName.of('MyCustomDashboardName'), }); // THEN diff --git a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts index 64d731b8b4e8d..3f3bea6386e23 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/test.graphs.ts @@ -133,7 +133,7 @@ export = { leftAnnotations: [{ value: 1000, color: '667788', - fill: Shading.Below, + fill: Shading.BELOW, label: 'this is the annotation', }] }); diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 5db6f1d39d076..d8e89469295db 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -335,7 +335,7 @@ const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', - groupName: 'MySecurityGroup', + securityGroupName: 'MySecurityGroup', vpc: vpc, }); new codebuild.Project(stack, 'MyProject', { diff --git a/packages/@aws-cdk/aws-codebuild/lib/cache.ts b/packages/@aws-cdk/aws-codebuild/lib/cache.ts index aac93ec29ebe9..b92206de862a6 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/cache.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/cache.ts @@ -17,17 +17,17 @@ export enum LocalCacheMode { /** * Caches Git metadata for primary and secondary sources */ - Source = 'LOCAL_SOURCE_CACHE', + SOURCE = 'LOCAL_SOURCE_CACHE', /** * Caches existing Docker layers */ - DockerLayer = 'LOCAL_DOCKER_LAYER_CACHE', + DOCKER_LAYER = 'LOCAL_DOCKER_LAYER_CACHE', /** * Caches directories you specify in the buildspec file */ - Custom = 'LOCAL_CUSTOM_CACHE', + CUSTOM = 'LOCAL_CUSTOM_CACHE', } /** diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 6a83b09440a73..7ae18f123e21e 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -5,7 +5,7 @@ import { DockerImageAsset, DockerImageAssetProps } from '@aws-cdk/aws-ecr-assets import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Aws, CfnResource, Construct, IResource, Lazy, PhysicalName, Resource, ResourceIdentifiers, Stack } from '@aws-cdk/cdk'; +import { Aws, CfnResource, Construct, Duration, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; import { Cache } from './cache'; @@ -448,9 +448,9 @@ export interface CommonProjectProps { * not complete. For valid values, see the timeoutInMinutes field in the AWS * CodeBuild User Guide. * - * @default 60 + * @default Duration.hours(1) */ - readonly timeout?: number; + readonly timeout?: Duration; /** * Additional environment variables to add to the build environment. @@ -691,8 +691,8 @@ export class Project extends ProjectBase { encryptionKey: props.encryptionKey && props.encryptionKey.keyArn, badgeEnabled: props.badge, cache: cache._toCloudFormation(), - name: this.physicalName.value, - timeoutInMinutes: props.timeout, + name: this.physicalName, + timeoutInMinutes: props.timeout && props.timeout.toMinutes(), secondarySources: Lazy.anyValue({ produce: () => this.renderSecondarySources() }), secondaryArtifacts: Lazy.anyValue({ produce: () => this.renderSecondaryArtifacts() }), triggers: sourceConfig.buildTriggers, @@ -701,13 +701,13 @@ export class Project extends ProjectBase { this.addVpcRequiredPermissions(props, resource); - const resourceIdentifiers = new ResourceIdentifiers(this, { + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ arn: resource.attrArn, - name: resource.refAsString, + name: resource.ref, arnComponents: { service: 'codebuild', resource: 'project', - resourceName: this.physicalName.value, + resourceName: this.physicalName, }, }); this.projectArn = resourceIdentifiers.arn; @@ -809,7 +809,7 @@ export class Project extends ProjectBase { computeType: env.computeType || this.buildImage.defaultComputeType, environmentVariables: !hasEnvironmentVars ? undefined : Object.keys(vars).map(name => ({ name, - type: vars[name].type || BuildEnvironmentVariableType.PlainText, + type: vars[name].type || BuildEnvironmentVariableType.PLAINTEXT, value: vars[name].value })) }; @@ -883,7 +883,7 @@ export class Project extends ProjectBase { })); const policy = new iam.Policy(this, 'PolicyDocument', { - policyName: 'CodeBuildEC2Policy', + policyName: PhysicalName.of('CodeBuildEC2Policy'), statements: [ new iam.PolicyStatement({ resources: ['*'], @@ -924,9 +924,9 @@ export class Project extends ProjectBase { * Build machine compute type. */ export enum ComputeType { - Small = 'BUILD_GENERAL1_SMALL', - Medium = 'BUILD_GENERAL1_MEDIUM', - Large = 'BUILD_GENERAL1_LARGE' + SMALL = 'BUILD_GENERAL1_SMALL', + MEDIUM = 'BUILD_GENERAL1_MEDIUM', + LARGE = 'BUILD_GENERAL1_LARGE' } export interface BuildEnvironment { @@ -1087,7 +1087,7 @@ export class LinuxBuildImage implements IBuildImage { } public readonly type = 'LINUX_CONTAINER'; - public readonly defaultComputeType = ComputeType.Small; + public readonly defaultComputeType = ComputeType.SMALL; private constructor(public readonly imageId: string) { } @@ -1179,14 +1179,14 @@ export class WindowsBuildImage implements IBuildImage { return image; } public readonly type = 'WINDOWS_CONTAINER'; - public readonly defaultComputeType = ComputeType.Medium; + public readonly defaultComputeType = ComputeType.MEDIUM; private constructor(public readonly imageId: string) { } public validate(buildEnvironment: BuildEnvironment): string[] { const ret: string[] = []; - if (buildEnvironment.computeType === ComputeType.Small) { + if (buildEnvironment.computeType === ComputeType.SMALL) { ret.push("Windows images do not support the Small ComputeType"); } return ret; @@ -1236,12 +1236,12 @@ export enum BuildEnvironmentVariableType { /** * An environment variable in plaintext format. */ - PlainText = 'PLAINTEXT', + PLAINTEXT = 'PLAINTEXT', /** * An environment variable stored in Systems Manager Parameter Store. */ - ParameterStore = 'PARAMETER_STORE' + PARAMETER_STORE = 'PARAMETER_STORE' } function ecrAccessForCodeBuildService(): iam.PolicyStatement { diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 43eb77c48043a..141e0620d4750 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -110,8 +110,10 @@ }, "awslint": { "exclude": [ - "construct-ctor-props-optional:@aws-cdk/aws-codebuild.Project" + "construct-ctor-props-optional:@aws-cdk/aws-codebuild.Project", + "duration-prop-type:@aws-cdk/aws-codebuild.PhaseChangeEvent.completedPhaseDurationSeconds", + "duration-prop-name:@aws-cdk/aws-codebuild.PhaseChangeEvent.completedPhaseDurationSeconds" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts index 12626a39514d0..6698aee216a1d 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts @@ -17,7 +17,7 @@ new codebuild.Project(stack, 'MyProject', { path: 'path/to/my/source.zip', }), environment: { - computeType: codebuild.ComputeType.Large + computeType: codebuild.ComputeType.LARGE } }); diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts index dd53529eb9748..d0998c2eca2a8 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts @@ -13,8 +13,8 @@ const vpc = new ec2.Vpc(stack, 'MyVPC', { const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { allowAllOutbound: true, description: 'Example', - groupName: 'Bob', - vpc, + securityGroupName: cdk.PhysicalName.of('Bob'), + vpc, }); new codebuild.Project(stack, 'MyProject', { buildSpec: codebuild.BuildSpec.fromObject({ diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index a9ff68a1cc876..a3f028a398b43 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -141,7 +141,9 @@ export = { 'with CodeCommit source'(test: Test) { const stack = new cdk.Stack(); - const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); + const repo = new codecommit.Repository(stack, 'MyRepo', { + repositoryName: 'hello-cdk', + }); const source = codebuild.Source.codeCommit({ repository: repo, cloneDepth: 2 }); @@ -628,7 +630,7 @@ export = { const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { - groupName: 'Bob', + securityGroupName: cdk.PhysicalName.of('Bob'), vpc, allowAllOutbound: true, description: 'Example', @@ -675,7 +677,7 @@ export = { const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { - groupName: 'Bob', + securityGroupName: cdk.PhysicalName.of('Bob'), vpc, allowAllOutbound: true, description: 'Example', @@ -697,7 +699,7 @@ export = { const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { - groupName: 'Bob', + securityGroupName: cdk.PhysicalName.of('Bob'), vpc, allowAllOutbound: true, description: 'Example', @@ -780,7 +782,7 @@ export = { name: 'some_name', bucket, }), - timeout: 123, + timeout: cdk.Duration.minutes(123), }); expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { @@ -1094,7 +1096,7 @@ export = { environment: { environmentVariables: { FOO: { value: '1234' }, - BAR: { value: `111${cdk.Token.asString({ twotwotwo: '222' })}`, type: codebuild.BuildEnvironmentVariableType.ParameterStore } + BAR: { value: `111${cdk.Token.asString({ twotwotwo: '222' })}`, type: codebuild.BuildEnvironmentVariableType.PARAMETER_STORE } } }, environmentVariables: { @@ -1183,7 +1185,7 @@ export = { const stack = new cdk.Stack(); const invalidEnvironment: codebuild.BuildEnvironment = { buildImage: codebuild.WindowsBuildImage.WIN_SERVER_CORE_2016_BASE, - computeType: codebuild.ComputeType.Small, + computeType: codebuild.ComputeType.SMALL, }; test.throws(() => { @@ -1207,7 +1209,9 @@ export = { shouldPassValidation: boolean } - const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); + const repo = new codecommit.Repository(stack, 'MyRepo', { + repositoryName: 'hello-cdk', + }); const bucket = new s3.Bucket(stack, 'MyBucket'); const cases: BadgeValidationTestCase[] = [ diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 65ea194a8b3e5..856dc11c552c7 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -196,8 +196,8 @@ export = { bucket: new Bucket(stack, 'Bucket'), path: 'path', }), - cache: codebuild.Cache.local(codebuild.LocalCacheMode.Custom, codebuild.LocalCacheMode.DockerLayer, - codebuild.LocalCacheMode.Source) + cache: codebuild.Cache.local(codebuild.LocalCacheMode.CUSTOM, codebuild.LocalCacheMode.DOCKER_LAYER, + codebuild.LocalCacheMode.SOURCE) }); // THEN diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 93281e104ada3..de6d194911384 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,5 +1,5 @@ import events = require('@aws-cdk/aws-events'); -import { Construct, IConstruct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, IConstruct, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnRepository } from './codecommit.generated'; export interface IRepository extends IResource { @@ -210,7 +210,9 @@ abstract class RepositoryBase extends Resource implements IRepository { export interface RepositoryProps { /** - * Name of the repository. This property is required for all repositories. + * Name of the repository. + * + * This property is required for all CodeCommit repositories. */ readonly repositoryName: string; @@ -270,21 +272,33 @@ export class Repository extends RepositoryBase { }); } + public readonly repositoryArn: string; + public readonly repositoryName: string; private readonly repository: CfnRepository; private readonly triggers = new Array(); constructor(scope: Construct, id: string, props: RepositoryProps) { - super(scope, id); + super(scope, id, { + physicalName: PhysicalName.of(props.repositoryName), + }); this.repository = new CfnRepository(this, 'Resource', { repositoryName: props.repositoryName, repositoryDescription: props.description, triggers: this.triggers }); - } - public get repositoryArn() { - return this.repository.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: this.repository.attrArn, + name: this.repository.attrName, + arnComponents: { + service: 'codecommit', + resource: props.repositoryName, + }, + }); + + this.repositoryArn = resourceIdentifiers.arn; + this.repositoryName = resourceIdentifiers.name; } public get repositoryCloneUrlHttp() { @@ -295,10 +309,6 @@ export class Repository extends RepositoryBase { return this.repository.attrCloneUrlSsh; } - public get repositoryName() { - return this.repository.attrName; - } - /** * Create a trigger to notify another service to run actions on repository events. * @param arn Arn of the resource that repository events will notify @@ -307,8 +317,8 @@ export class Repository extends RepositoryBase { public notify(arn: string, options?: RepositoryTriggerOptions): Repository { let evt = options && options.events; - if (evt && evt.length > 1 && evt.indexOf(RepositoryEventTrigger.All) > -1) { - evt = [RepositoryEventTrigger.All]; + if (evt && evt.length > 1 && evt.indexOf(RepositoryEventTrigger.ALL) > -1) { + evt = [RepositoryEventTrigger.ALL]; } const customData = options && options.customData; @@ -328,7 +338,7 @@ export class Repository extends RepositoryBase { name, customData, branches, - events: evt || [RepositoryEventTrigger.All], + events: evt || [RepositoryEventTrigger.ALL], }); return this; } @@ -368,8 +378,8 @@ export interface RepositoryTriggerOptions { * Repository events that will cause the trigger to run actions in another service. */ export enum RepositoryEventTrigger { - All = 'all', - UpdateRef = 'updateReference', - CreateRef = 'createReference', - DeleteRef = 'deleteReference' + ALL = 'all', + UPDATE_REF = 'updateReference', + CREATE_REF = 'createReference', + DELETE_REF = 'deleteReference' } diff --git a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-events.ts b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-events.ts index 9e576c0f6a36a..184e53534c669 100644 --- a/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-events.ts +++ b/packages/@aws-cdk/aws-codecommit/test/integ.codecommit-events.ts @@ -5,7 +5,9 @@ import codecommit = require('../lib'); const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codecommit-events'); -const repo = new codecommit.Repository(stack, 'Repo', { repositoryName: 'aws-cdk-codecommit-events' }); +const repo = new codecommit.Repository(stack, 'Repo', { + repositoryName: 'aws-cdk-codecommit-events', +}); const topic = new sns.Topic(stack, 'MyTopic'); // we can't use @aws-cdk/aws-events-targets.SnsTopic here because it will diff --git a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts index abd3fa373c48d..5e8e8a846399c 100644 --- a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts +++ b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts @@ -9,7 +9,7 @@ export = { const stack = new Stack(); const props: RepositoryProps = { - repositoryName: 'MyRepository' + repositoryName: 'MyRepository', }; const snsArn = 'arn:aws:sns:*:123456789012:my_topic'; @@ -42,8 +42,9 @@ export = { 'fails when triggers have duplicate names'(test: Test) { const stack = new Stack(); - const props = { repositoryName: 'MyRepository' }; - const myRepository = new Repository(stack, 'MyRepository', props).notify('myTrigger'); + const myRepository = new Repository(stack, 'MyRepository', { + repositoryName: 'MyRepository', + }).notify('myTrigger'); test.throws(() => myRepository.notify('myTrigger')); diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts index a0a795858f209..f66aae4f7d516 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnApplication } from "../codedeploy.generated"; import { arnForApplication } from "../utils"; @@ -29,7 +29,7 @@ export interface LambdaApplicationProps { * * @default an auto-generated name will be used */ - readonly applicationName?: string; + readonly applicationName?: PhysicalName; } /** @@ -60,14 +60,26 @@ export class LambdaApplication extends Resource implements ILambdaApplication { public readonly applicationName: string; constructor(scope: Construct, id: string, props: LambdaApplicationProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.applicationName, + }); const resource = new CfnApplication(this, 'Resource', { - applicationName: props.applicationName, + applicationName: this.physicalName, computePlatform: 'Lambda' }); - this.applicationName = resource.refAsString; - this.applicationArn = arnForApplication(this.applicationName); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForApplication(resource.ref), + name: resource.ref, + arnComponents: { + service: 'codedeploy', + resource: 'application', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.applicationName = resourceIdentifiers.name; + this.applicationArn = resourceIdentifiers.arn; } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index fc3cfad6f4179..1c921a862d40a 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -47,7 +47,7 @@ export interface LambdaDeploymentGroupProps { * * @default - An auto-generated name will be used. */ - readonly deploymentGroupName?: string; + readonly deploymentGroupName?: cdk.PhysicalName; /** * The Deployment Configuration this Deployment Group uses. @@ -142,7 +142,9 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy private postHook?: lambda.IFunction; constructor(scope: cdk.Construct, id: string, props: LambdaDeploymentGroupProps) { - super(scope, id); + super(scope, id, { + physicalName: props.deploymentGroupName, + }); this.application = props.application || new LambdaApplication(this, 'Application'); this.alarms = props.alarms || []; @@ -156,7 +158,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy const resource = new CfnDeploymentGroup(this, 'Resource', { applicationName: this.application.applicationName, serviceRoleArn: this.role.roleArn, - deploymentGroupName: props.deploymentGroupName, + deploymentGroupName: this.physicalName, deploymentConfigName: (props.deploymentConfig || LambdaDeploymentConfig.AllAtOnce).deploymentConfigName, deploymentStyle: { deploymentType: 'BLUE_GREEN', @@ -166,8 +168,18 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy autoRollbackConfiguration: cdk.Lazy.anyValue({ produce: () => renderAutoRollbackConfiguration(this.alarms, props.autoRollback) }), }); - this.deploymentGroupName = resource.refAsString; - this.deploymentGroupArn = arnForDeploymentGroup(this.application.applicationName, this.deploymentGroupName); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForDeploymentGroup(this.application.applicationName, resource.ref), + name: resource.ref, + arnComponents: { + service: 'codedeploy', + resource: 'deploymentgroup', + resourceName: `${this.application.applicationName}/${this.physicalName}`, + sep: ':', + }, + }); + this.deploymentGroupName = resourceIdentifiers.name; + this.deploymentGroupArn = resourceIdentifiers.arn; if (props.preHook) { this.addPreHook(props.preHook); @@ -176,10 +188,10 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy this.addPostHook(props.postHook); } - (props.alias.node.findChild('Resource') as lambda.CfnAlias).options.updatePolicy = { + (props.alias.node.defaultChild as lambda.CfnAlias).options.updatePolicy = { codeDeployLambdaAliasUpdate: { applicationName: this.application.applicationName, - deploymentGroupName: resource.refAsString, + deploymentGroupName: resource.ref, beforeAllowTrafficHook: cdk.Lazy.stringValue({ produce: () => this.preHook && this.preHook.functionName }), afterAllowTrafficHook: cdk.Lazy.stringValue({ produce: () => this.postHook && this.postHook.functionName }), } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts index 14c4beb231898..cf2c0830b1687 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnApplication } from '../codedeploy.generated'; import { arnForApplication } from '../utils'; @@ -29,7 +29,7 @@ export interface ServerApplicationProps { * * @default an auto-generated name will be used */ - readonly applicationName?: string; + readonly applicationName?: PhysicalName; } /** @@ -61,14 +61,26 @@ export class ServerApplication extends Resource implements IServerApplication { public readonly applicationName: string; constructor(scope: Construct, id: string, props: ServerApplicationProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.applicationName, + }); const resource = new CfnApplication(this, 'Resource', { - applicationName: props.applicationName, + applicationName: this.physicalName, computePlatform: 'Server', }); - this.applicationName = resource.refAsString; - this.applicationArn = arnForApplication(this.applicationName); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForApplication(resource.ref), + name: resource.ref, + arnComponents: { + service: 'codedeploy', + resource: 'application', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.applicationName = resourceIdentifiers.name; + this.applicationArn = resourceIdentifiers.arn; } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts index 96fd860d7a644..5ac99e284ec7b 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts @@ -65,7 +65,7 @@ export interface ServerDeploymentConfigProps { * * @default a name will be auto-generated */ - readonly deploymentConfigName?: string; + readonly deploymentConfigName?: cdk.PhysicalName; /** * Minimum number of healthy hosts. @@ -106,14 +106,16 @@ export class ServerDeploymentConfig extends cdk.Resource implements IServerDeplo public readonly deploymentConfigArn: string; constructor(scope: cdk.Construct, id: string, props: ServerDeploymentConfigProps) { - super(scope, id); + super(scope, id, { + physicalName: props.deploymentConfigName, + }); const resource = new CfnDeploymentConfig(this, 'Resource', { - deploymentConfigName: props.deploymentConfigName, + deploymentConfigName: this.physicalName, minimumHealthyHosts: props.minimumHealthyHosts._json, }); - this.deploymentConfigName = resource.refAsString; + this.deploymentConfigName = resource.ref; this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName); } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 77d5831cef0b9..f48fa9648f67f 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -4,7 +4,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { CfnDeploymentGroup } from '../codedeploy.generated'; import { AutoRollbackConfig } from '../rollback-config'; import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../utils'; @@ -73,8 +73,8 @@ abstract class ServerDeploymentGroupBase extends cdk.Resource implements IServer public readonly deploymentConfig: IServerDeploymentConfig; public abstract readonly autoScalingGroups?: autoscaling.AutoScalingGroup[]; - constructor(scope: cdk.Construct, id: string, deploymentConfig?: IServerDeploymentConfig) { - super(scope, id); + constructor(scope: cdk.Construct, id: string, deploymentConfig?: IServerDeploymentConfig, props?: cdk.ResourceProps) { + super(scope, id, props); this.deploymentConfig = deploymentConfig || ServerDeploymentConfig.OneAtATime; } } @@ -153,7 +153,7 @@ export interface ServerDeploymentGroupProps { * * @default - An auto-generated name will be used. */ - readonly deploymentGroupName?: string; + readonly deploymentGroupName?: PhysicalName; /** * The EC2/on-premise Deployment Configuration to use for this Deployment Group. @@ -266,7 +266,9 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { private readonly alarms: cloudwatch.IAlarm[]; constructor(scope: cdk.Construct, id: string, props: ServerDeploymentGroupProps = {}) { - super(scope, id, props.deploymentConfig); + super(scope, id, props.deploymentConfig, { + physicalName: props.deploymentGroupName, + }); this.application = props.application || new ServerApplication(this, 'Application'); @@ -286,7 +288,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { const resource = new CfnDeploymentGroup(this, 'Resource', { applicationName: this.application.applicationName, - deploymentGroupName: props.deploymentGroupName, + deploymentGroupName: this.physicalName, serviceRoleArn: this.role.roleArn, deploymentConfigName: props.deploymentConfig && props.deploymentConfig.deploymentConfigName, @@ -303,8 +305,18 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { autoRollbackConfiguration: cdk.Lazy.anyValue({ produce: () => renderAutoRollbackConfiguration(this.alarms, props.autoRollback) }), }); - this.deploymentGroupName = resource.refAsString; - this.deploymentGroupArn = arnForDeploymentGroup(this.application.applicationName, this.deploymentGroupName); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForDeploymentGroup(this.application.applicationName, resource.ref), + name: resource.ref, + arnComponents: { + service: 'codedeploy', + resource: 'deploymentgroup', + resourceName: `${this.application.applicationName}/${this.physicalName}`, + sep: ':', + }, + }); + this.deploymentGroupName = resourceIdentifiers.name; + this.deploymentGroupArn = resourceIdentifiers.arn; } /** @@ -340,7 +352,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { this.codeDeployBucket.grantRead(asg.role, 'latest/*'); switch (asg.osType) { - case ec2.OperatingSystemType.Linux: + case ec2.OperatingSystemType.LINUX: asg.addUserData( 'PKG_CMD=`which yum 2>/dev/null`', 'if [ -z "$PKG_CMD" ]; then', @@ -362,7 +374,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { 'rm -fr $TMP_DIR', ); break; - case ec2.OperatingSystemType.Windows: + case ec2.OperatingSystemType.WINDOWS: asg.addUserData( 'Set-Variable -Name TEMPDIR -Value (New-TemporaryFile).DirectoryName', `aws s3 cp s3://aws-codedeploy-${Stack.of(this).region}/latest/codedeploy-agent.msi $TEMPDIR\\codedeploy-agent.msi`, diff --git a/packages/@aws-cdk/aws-codedeploy/lib/utils.ts b/packages/@aws-cdk/aws-codedeploy/lib/utils.ts index 717b3cfd1b679..ab720f48ef26b 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/utils.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/utils.ts @@ -27,9 +27,9 @@ export function renderAlarmConfiguration(alarms: cloudwatch.IAlarm[], ignorePoll } enum AutoRollbackEvent { - DeploymentFailure = 'DEPLOYMENT_FAILURE', - DeploymentStopOnAlarm = 'DEPLOYMENT_STOP_ON_ALARM', - DeploymentStopOnRequest = 'DEPLOYMENT_STOP_ON_REQUEST' + DEPLOYMENT_FAILURE = 'DEPLOYMENT_FAILURE', + DEPLOYMENT_STOP_ON_ALARM = 'DEPLOYMENT_STOP_ON_ALARM', + DEPLOYMENT_STOP_ON_REQUEST = 'DEPLOYMENT_STOP_ON_REQUEST' } export function renderAutoRollbackConfiguration(alarms: cloudwatch.IAlarm[], autoRollbackConfig: AutoRollbackConfig = {}): @@ -38,19 +38,19 @@ export function renderAutoRollbackConfiguration(alarms: cloudwatch.IAlarm[], aut // we roll back failed deployments by default if (autoRollbackConfig.failedDeployment !== false) { - events.push(AutoRollbackEvent.DeploymentFailure); + events.push(AutoRollbackEvent.DEPLOYMENT_FAILURE); } // we _do not_ roll back stopped deployments by default if (autoRollbackConfig.stoppedDeployment === true) { - events.push(AutoRollbackEvent.DeploymentStopOnRequest); + events.push(AutoRollbackEvent.DEPLOYMENT_STOP_ON_REQUEST); } // we _do not_ roll back alarm-triggering deployments by default // unless the Deployment Group has at least one alarm if (autoRollbackConfig.deploymentInAlarm !== false) { if (alarms.length > 0) { - events.push(AutoRollbackEvent.DeploymentStopOnAlarm); + events.push(AutoRollbackEvent.DEPLOYMENT_STOP_ON_ALARM); } else if (autoRollbackConfig.deploymentInAlarm === true) { throw new Error( "The auto-rollback setting 'deploymentInAlarm' does not have any effect unless you associate " + diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 9db042158339e..7eddb78888b8f 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -104,4 +104,4 @@ ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.ts index e5ba28c096c85..72eb4f69d6cff 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.ts @@ -15,7 +15,7 @@ const handler = new lambda.Function(stack, `Handler`, { }); const version = handler.addVersion('1'); const blueGreenAlias = new lambda.Alias(stack, `Alias`, { - aliasName: `alias`, + aliasName: 'alias', version }); @@ -35,7 +35,7 @@ new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', { deploymentConfig: codedeploy.LambdaDeploymentConfig.Linear10PercentEvery1Minute, alarms: [ new cloudwatch.Alarm(stack, 'BlueGreenErrors', { - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1, metric: blueGreenAlias.metricErrors() diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/test.application.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.application.ts index d81aa9b3e76e2..573b389b81627 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/test.application.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.application.ts @@ -16,7 +16,7 @@ export = { "can be created with explicit name"(test: Test) { const stack = new cdk.Stack(); new codedeploy.LambdaApplication(stack, 'MyApp', { - applicationName: 'my-name' + applicationName: cdk.PhysicalName.of('my-name'), }); expect(stack).to(haveResource('AWS::CodeDeploy::Application', { ApplicationName: 'my-name', diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.deployment-group.ts index 9911dfd4193ca..c9fe4e5a35eaa 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/test.deployment-group.ts @@ -119,7 +119,7 @@ export = { application, alias, deploymentConfig: LambdaDeploymentConfig.AllAtOnce, - deploymentGroupName: 'test' + deploymentGroupName: cdk.PhysicalName.of('test'), }); expect(stack).to(haveResourceLike('AWS::CodeDeploy::DeploymentGroup', { @@ -215,7 +215,7 @@ export = { deploymentConfig: codedeploy.LambdaDeploymentConfig.AllAtOnce, alarms: [new cloudwatch.Alarm(stack, 'Failures', { metric: alias.metricErrors(), - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1 })] @@ -471,7 +471,7 @@ export = { ignorePollAlarmsFailure: true, alarms: [new cloudwatch.Alarm(stack, 'Failures', { metric: alias.metricErrors(), - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1 })] @@ -564,7 +564,7 @@ export = { }, alarms: [new cloudwatch.Alarm(stack, 'Failures', { metric: alias.metricErrors(), - comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, threshold: 1, evaluationPeriods: 1 })] diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.ts index ebda4e4675cb7..4e10fcc064567 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/integ.deployment-group.ts @@ -12,7 +12,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codedeploy-server-dg'); const vpc = new ec2.Vpc(stack, 'VPC'); const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M5, ec2.InstanceSize.Large), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE), machineImage: new ec2.AmazonLinuxImage(), vpc, }); diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts index 8267d104c16fb..ecfd55aa4a6cc 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts @@ -46,7 +46,7 @@ export = { const stack = new cdk.Stack(); const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Standard3, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.STANDARD3, ec2.InstanceSize.SMALL), machineImage: new ec2.AmazonLinuxImage(), vpc: new ec2.Vpc(stack, 'VPC'), }); @@ -70,7 +70,7 @@ export = { const stack = new cdk.Stack(); const asg = new autoscaling.AutoScalingGroup(stack, 'ASG', { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Standard3, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.STANDARD3, ec2.InstanceSize.SMALL), machineImage: new ec2.AmazonLinuxImage(), vpc: new ec2.Vpc(stack, 'VPC'), }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts index ad36ab6e614b0..7e70ccb423d6f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts @@ -43,7 +43,7 @@ export class AlexaSkillDeployAction extends codepipeline.Action { constructor(props: AlexaSkillDeployActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Deploy, + category: codepipeline.ActionCategory.DEPLOY, owner: 'ThirdParty', provider: 'AlexaSkillsKit', artifactBounds: { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index 683c0dc65e43f..5721604717374 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -1,4 +1,5 @@ import cloudformation = require('@aws-cdk/aws-cloudformation'); +import { CloudFormationCapabilities } from '@aws-cdk/aws-cloudformation'; import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); @@ -74,7 +75,7 @@ export abstract class CloudFormationAction extends codepipeline.Action { ? [props.output || new codepipeline.Artifact(`${props.actionName}_${props.stackName}_Artifact`)] : undefined, provider: 'CloudFormation', - category: codepipeline.ActionCategory.Deploy, + category: codepipeline.ActionCategory.DEPLOY, configuration: { StackName: props.stackName, OutputFileName: props.outputFileName, @@ -141,7 +142,7 @@ export interface CloudFormationDeployActionProps extends CloudFormationActionPro * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities * @default None, unless `adminPermissions` is true */ - readonly capabilities?: cloudformation.CloudFormationCapabilities; + readonly capabilities?: cloudformation.CloudFormationCapabilities[]; /** * Whether to grant full permissions to CloudFormation while deploying this template. @@ -221,12 +222,12 @@ export abstract class CloudFormationDeployAction extends CloudFormationAction { constructor(props: CloudFormationDeployActionProps, configuration: any) { const capabilities = props.adminPermissions && props.capabilities === undefined - ? cloudformation.CloudFormationCapabilities.NamedIAM + ? [cloudformation.CloudFormationCapabilities.NamedIAM] : props.capabilities; super(props, { ...configuration, // None evaluates to empty string which is falsey and results in undefined - Capabilities: (capabilities && capabilities.toString()) || undefined, + Capabilities: parseCapabilities(capabilities), RoleArn: cdk.Lazy.stringValue({ produce: () => this.deploymentRole.roleArn }), ParameterOverrides: cdk.Lazy.stringValue({ produce: () => Stack.of(this.scope).toJsonString(props.parameterOverrides) }), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, @@ -543,3 +544,16 @@ interface StatementTemplate { } type StatementCondition = { [op: string]: { [attribute: string]: string } }; + +function parseCapabilities(capabilities: CloudFormationCapabilities[] | undefined): string | undefined { + if (capabilities === undefined) { + return undefined; + } else if (capabilities.length === 1) { + const capability = capabilities.toString(); + return (capability === '') ? undefined : capability; + } else if (capabilities.length > 1) { + return capabilities.join(','); + } + + return undefined; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts index 2826f438c67db..7de9abd2922d4 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/build-action.ts @@ -71,8 +71,8 @@ export class CodeBuildAction extends codepipeline.Action { super({ ...props, category: props.type === CodeBuildActionType.TEST - ? codepipeline.ActionCategory.Test - : codepipeline.ActionCategory.Build, + ? codepipeline.ActionCategory.TEST + : codepipeline.ActionCategory.BUILD, provider: 'CodeBuild', artifactBounds: { minInputs: 1, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, inputs: [props.input, ...props.extraInputs || []], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 32278f12ac0a7..8368ec9af90c8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -41,7 +41,7 @@ export class CodeCommitSourceAction extends codepipeline.Action { constructor(props: CodeCommitSourceActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'CodeCommit', artifactBounds: sourceArtifactBounds(), outputs: [props.output], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts index 2456af68e1dc1..7d1b0b9c0e04c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts @@ -24,7 +24,7 @@ export class CodeDeployServerDeployAction extends codepipeline.Action { constructor(props: CodeDeployServerDeployActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Deploy, + category: codepipeline.ActionCategory.DEPLOY, provider: 'CodeDeploy', artifactBounds: deployArtifactBounds(), inputs: [props.input], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts index 22aa8d7e80ede..4ec55df41d1ed 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts @@ -39,7 +39,7 @@ export class EcrSourceAction extends codepipeline.Action { constructor(props: EcrSourceActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'ECR', artifactBounds: sourceArtifactBounds(), outputs: [props.output], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts index 1ae0f05333936..e52a2e1db8935 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts @@ -47,12 +47,12 @@ export class EcsDeployAction extends codepipeline.Action { constructor(props: EcsDeployActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Deploy, + category: codepipeline.ActionCategory.DEPLOY, provider: 'ECS', artifactBounds: deployArtifactBounds(), inputs: [determineInputArtifact(props)], configuration: { - ClusterName: props.service.clusterName, + ClusterName: props.service.cluster.clusterName, ServiceName: props.service.serviceName, FileName: props.imageFile && props.imageFile.fileName, }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts index 51d85c2edd80d..02216174ba02d 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts @@ -68,7 +68,7 @@ export class GitHubSourceAction extends codepipeline.Action { constructor(props: GitHubSourceActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, owner: 'ThirdParty', provider: 'GitHub', artifactBounds: sourceArtifactBounds(), diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts index 24e3c948c5227..f23dfbb4bff63 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-action.ts @@ -64,8 +64,8 @@ export class JenkinsAction extends codepipeline.Action { super({ ...props, category: props.type === JenkinsActionType.BUILD - ? codepipeline.ActionCategory.Build - : codepipeline.ActionCategory.Test, + ? codepipeline.ActionCategory.BUILD + : codepipeline.ActionCategory.TEST, provider: props.jenkinsProvider.providerName, owner: 'Custom', artifactBounds: jenkinsArtifactsBounds, @@ -79,7 +79,7 @@ export class JenkinsAction extends codepipeline.Action { } protected bind(_info: codepipeline.ActionBind): void { - if (this.category === codepipeline.ActionCategory.Build) { + if (this.category === codepipeline.ActionCategory.BUILD) { this.jenkinsProvider._registerBuildProvider(); } else { this.jenkinsProvider._registerTestProvider(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts index 322235f37f6ef..1d14f9361c709 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts @@ -170,7 +170,7 @@ export class JenkinsProvider extends BaseJenkinsProvider { return; } this.buildIncluded = true; - this.registerJenkinsCustomAction('JenkinsBuildProviderResource', codepipeline.ActionCategory.Build); + this.registerJenkinsCustomAction('JenkinsBuildProviderResource', codepipeline.ActionCategory.BUILD); } /** @@ -181,7 +181,7 @@ export class JenkinsProvider extends BaseJenkinsProvider { return; } this.testIncluded = true; - this.registerJenkinsCustomAction('JenkinsTestProviderResource', codepipeline.ActionCategory.Test); + this.registerJenkinsCustomAction('JenkinsTestProviderResource', codepipeline.ActionCategory.TEST); } private registerJenkinsCustomAction(id: string, category: codepipeline.ActionCategory) { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index 4e248b9c76e42..6c864e329049c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -59,7 +59,7 @@ export class LambdaInvokeAction extends codepipeline.Action { constructor(props: LambdaInvokeActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Invoke, + category: codepipeline.ActionCategory.INVOKE, provider: 'Lambda', artifactBounds: { minInputs: 0, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts index a6a65e58624d3..ea679ac648cfb 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts @@ -40,7 +40,7 @@ export class ManualApprovalAction extends codepipeline.Action { constructor(props: ManualApprovalActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Approval, + category: codepipeline.ActionCategory.APPROVAL, provider: 'Manual', artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 0, maxOutputs: 0 }, configuration: cdk.Lazy.anyValue({ produce: () => this.actionConfiguration() }), diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts index 1cda99264290b..59c96ea4f883a 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts @@ -38,7 +38,7 @@ export class S3DeployAction extends codepipeline.Action { constructor(props: S3DeployActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Deploy, + category: codepipeline.ActionCategory.DEPLOY, provider: 'S3', artifactBounds: deployArtifactBounds(), inputs: [props.input], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index eb4065cb0f3ff..91113d7749e82 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -48,7 +48,7 @@ export class S3SourceAction extends codepipeline.Action { constructor(props: S3SourceActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'S3', artifactBounds: sourceArtifactBounds(), outputs: [props.output], diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json index ddfd1474422df..7269e675e43d8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline-actions", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index e9a19f3a8216b..fa9f684faab68 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { CloudFormationCapabilities } from '@aws-cdk/aws-cloudformation'; import codebuild = require('@aws-cdk/aws-codebuild'); import { Repository } from '@aws-cdk/aws-codecommit'; import codepipeline = require('@aws-cdk/aws-codepipeline'); @@ -21,7 +22,9 @@ export = { }); /** Source! */ - const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + const repo = new Repository(stack, 'MyVeryImportantRepo', { + repositoryName: 'my-very-important-repo', + }); const sourceOutput = new codepipeline.Artifact('SourceArtifact'); const source = new cpactions.CodeCommitSourceAction({ @@ -411,7 +414,136 @@ export = { })); test.done(); - } + }, + + 'Single capability is passed to template'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + capabilities: [ + CloudFormationCapabilities.NamedIAM + ] + })); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has named IAM capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "Capabilities": "CAPABILITY_NAMED_IAM", + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, + + 'Multiple capabilities are passed to template'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + capabilities: [ + CloudFormationCapabilities.NamedIAM, + CloudFormationCapabilities.AutoExpand + ] + })); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has named IAM and AUTOEXPAND capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, + + 'Empty capabilities is not passed to template'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + capabilities: [ + CloudFormationCapabilities.None + ] + })); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has no capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, }; /** @@ -430,7 +562,9 @@ class TestFixture extends cdk.Stack { this.pipeline = new codepipeline.Pipeline(this, 'Pipeline'); this.sourceStage = this.pipeline.addStage({ stageName: 'Source' }); this.deployStage = this.pipeline.addStage({ stageName: 'Deploy' }); - this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + this.repo = new Repository(this, 'MyVeryImportantRepo', { + repositoryName: 'my-very-important-repo', + }); this.sourceOutput = new codepipeline.Artifact('SourceArtifact'); const source = new cpactions.CodeCommitSourceAction({ actionName: 'Source', diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts index d0062f82a0eba..0e1d7dec0bc12 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts @@ -9,7 +9,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); /// !show // Source stage: read from repository const repo = new codecommit.Repository(stack, 'TemplateRepo', { - repositoryName: 'template-repo' + repositoryName: 'template-repo', }); const sourceOutput = new codepipeline.Artifact('SourceArtifact'); const source = new cpactions.CodeCommitSourceAction({ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts index a4add3b48f41f..901d563dc88fb 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts @@ -25,13 +25,17 @@ const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline'); // and the source code of the Lambda Function, if they're separate const cdkSourceOutput = new codepipeline.Artifact(); const cdkSourceAction = new codepipeline_actions.CodeCommitSourceAction({ - repository: new codecommit.Repository(pipelineStack, 'CdkCodeRepo', { repositoryName: 'CdkCodeRepo' }), + repository: new codecommit.Repository(pipelineStack, 'CdkCodeRepo', { + repositoryName: 'CdkCodeRepo', + }), actionName: 'CdkCode_Source', output: cdkSourceOutput, }); const lambdaSourceOutput = new codepipeline.Artifact(); const lambdaSourceAction = new codepipeline_actions.CodeCommitSourceAction({ - repository: new codecommit.Repository(pipelineStack, 'LambdaCodeRepo', { repositoryName: 'LambdaCodeRepo' }), + repository: new codecommit.Repository(pipelineStack, 'LambdaCodeRepo', { + repositoryName: 'LambdaCodeRepo', + }), actionName: 'LambdaCode_Source', output: lambdaSourceOutput, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts index 42053330fe295..5219adc82afce 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts @@ -18,7 +18,7 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', { }); const key = 'key'; const trail = new cloudtrail.Trail(stack, 'CloudTrail'); -trail.addS3EventSelector([bucket.arnForObjects(key)], { readWriteType: cloudtrail.ReadWriteType.WriteOnly, includeManagementEvents: false }); +trail.addS3EventSelector([bucket.arnForObjects(key)], { readWriteType: cloudtrail.ReadWriteType.WRITE_ONLY, includeManagementEvents: false }); sourceStage.addAction(new cpactions.S3SourceAction({ actionName: 'Source', output: new codepipeline.Artifact('SourceArtifact'), diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts index 54063d0f83c82..37caee0fe9f3f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts @@ -7,7 +7,9 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codecommit'); -const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'my-repo' }); +const repo = new codecommit.Repository(stack, 'MyRepo', { + repositoryName: 'my-repo', +}); new codepipeline.Pipeline(stack, 'Pipeline', { stages: [ diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts index 61d9c479374c6..162bf49f700a5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts @@ -9,7 +9,7 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codedeploy'); const application = new codedeploy.ServerApplication(stack, 'CodeDeployApplication', { - applicationName: 'IntegTestDeployApp', + applicationName: cdk.PhysicalName.of('IntegTestDeployApp'), }); const deploymentConfig = new codedeploy.ServerDeploymentConfig(stack, 'CustomDeployConfig', { @@ -18,7 +18,7 @@ const deploymentConfig = new codedeploy.ServerDeploymentConfig(stack, 'CustomDep const deploymentGroup = new codedeploy.ServerDeploymentGroup(stack, 'CodeDeployGroup', { application, - deploymentGroupName: 'IntegTestDeploymentGroup', + deploymentGroupName: cdk.PhysicalName.of('IntegTestDeploymentGroup'), deploymentConfig, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts index 6f5838b0aaac2..47a6d2afc9776 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts @@ -16,7 +16,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-pipeline-event-target'); const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); const repository = new codecommit.Repository(stack, 'CodeCommitRepo', { - repositoryName: 'foo' + repositoryName: 'foo', }); const project = new codebuild.PipelineProject(stack, 'BuildProject'); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index b4534a812c748..f34ef11dc7c58 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -54,7 +54,7 @@ export = { const stack = new Stack(undefined, 'StackName'); new codepipeline.Pipeline(stack, 'Pipeline', { - pipelineName: Aws.stackName, + pipelineName: PhysicalName.of(Aws.stackName), }); expect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -726,7 +726,7 @@ export = { }); const sourceBucket = new s3.Bucket(pipelineStack, 'ArtifactBucket', { bucketName: PhysicalName.of('source-bucket'), - encryption: s3.BucketEncryption.Kms, + encryption: s3.BucketEncryption.KMS, }); const sourceOutput = new codepipeline.Artifact(); new codepipeline.Pipeline(pipelineStack, 'Pipeline', { @@ -870,6 +870,6 @@ function stageForTesting(stack: Stack): codepipeline.IStage { function repositoryForTesting(stack: Stack): codecommit.Repository { return new codecommit.Repository(stack, 'Repository', { - repositoryName: 'Repository' + repositoryName: 'Repository', }); } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts index 7d1916742dbe3..d69e347061882 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -5,12 +5,12 @@ import { Artifact } from './artifact'; import validation = require('./validation'); export enum ActionCategory { - Source = 'Source', - Build = 'Build', - Test = 'Test', - Approval = 'Approval', - Deploy = 'Deploy', - Invoke = 'Invoke' + SOURCE = 'Source', + BUILD = 'Build', + TEST = 'Test', + APPROVAL = 'Approval', + DEPLOY = 'Deploy', + INVOKE = 'Invoke' } /** diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index b28f95235119f..35bbad18dab7d 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -78,7 +78,7 @@ export interface PipelineProps { * * @default - AWS CloudFormation generates an ID and uses that for the pipeline name. */ - readonly pipelineName?: string; + readonly pipelineName?: PhysicalName; /** * A map of region to S3 bucket name used for cross-region CodePipeline. @@ -219,9 +219,11 @@ export class Pipeline extends PipelineBase { private readonly _crossRegionScaffoldStacks: { [region: string]: CrossRegionScaffoldStack } = {}; constructor(scope: Construct, id: string, props: PipelineProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.pipelineName, + }); - validateName('Pipeline', props.pipelineName); + validateName('Pipeline', this.physicalName); // If a bucket has been provided, use it - otherwise, create a bucket. let propsBucket = props.artifactBucket; @@ -230,7 +232,7 @@ export class Pipeline extends PipelineBase { propsBucket = new s3.Bucket(this, 'ArtifactsBucket', { bucketName: PhysicalName.auto({ crossEnvironment: true }), encryptionKey, - encryption: s3.BucketEncryption.Kms, + encryption: s3.BucketEncryption.KMS, removalPolicy: RemovalPolicy.Retain }); } @@ -247,7 +249,7 @@ export class Pipeline extends PipelineBase { stages: Lazy.anyValue({ produce: () => this.renderStages() }), roleArn: this.role.roleArn, restartExecutionOnUpdate: props && props.restartExecutionOnUpdate, - name: props && props.pipelineName, + name: this.physicalName, }); // this will produce a DependsOn for both the role and the policy resources. @@ -255,7 +257,15 @@ export class Pipeline extends PipelineBase { this.artifactBucket.grantReadWrite(this.role); - this.pipelineName = codePipeline.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: '', + name: codePipeline.ref, + arnComponents: { + service: 'codepipeline', + resource: this.physicalName, + }, + }); + this.pipelineName = resourceIdentifiers.name; this.pipelineVersion = codePipeline.attrVersion; this.crossRegionReplicationBuckets = props.crossRegionReplicationBuckets || {}; this.artifactStores = {}; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts index 2853165951ab6..29f7fd785c13a 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts @@ -26,7 +26,7 @@ export function validateArtifactBounds( type: string, artifacts: Artifact[], * in the first stage of a pipeline, and the first stage can only contain source actions. */ export function validateSourceAction(mustBeSource: boolean, category: string, actionName: string, stageName: string): string[] { - if (mustBeSource !== (category === ActionCategory.Source)) { + if (mustBeSource !== (category === ActionCategory.SOURCE)) { return [`Action ${actionName} in stage ${stageName}: ` + (mustBeSource ? 'first stage may only contain Source actions' : 'Source actions may only occur in first stage')]; } diff --git a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts index cb14d68eef1d4..a04c7acd7ef1b 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/fake-build-action.ts @@ -12,7 +12,7 @@ export class FakeBuildAction extends codepipeline.Action { constructor(props: FakeBuildActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Build, + category: codepipeline.ActionCategory.BUILD, provider: 'Fake', artifactBounds: { minInputs: 1, maxInputs: 3, minOutputs: 0, maxOutputs: 1 }, inputs: [props.input, ...props.extraInputs || []], diff --git a/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts b/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts index b71558655b088..8f1f1f7c968f1 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/fake-source-action.ts @@ -10,7 +10,7 @@ export class FakeSourceAction extends codepipeline.Action { constructor(props: FakeSourceActionProps) { super({ ...props, - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'Fake', artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 4 }, outputs: [props.output, ...props.extraOutputs || []], diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts index 54e8e78a2936b..42e0d893e58f5 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts @@ -35,27 +35,27 @@ export = { 'action type validation': { 'must be source and is source'(test: Test) { - const result = validations.validateSourceAction(true, codepipeline.ActionCategory.Source, 'test action', 'test stage'); + const result = validations.validateSourceAction(true, codepipeline.ActionCategory.SOURCE, 'test action', 'test stage'); test.deepEqual(result.length, 0); test.done(); }, 'must be source and is not source'(test: Test) { - const result = validations.validateSourceAction(true, codepipeline.ActionCategory.Deploy, 'test action', 'test stage'); + const result = validations.validateSourceAction(true, codepipeline.ActionCategory.DEPLOY, 'test action', 'test stage'); test.deepEqual(result.length, 1); test.ok(result[0].match(/may only contain Source actions/), 'the validation should have failed'); test.done(); }, 'cannot be source and is source'(test: Test) { - const result = validations.validateSourceAction(false, codepipeline.ActionCategory.Source, 'test action', 'test stage'); + const result = validations.validateSourceAction(false, codepipeline.ActionCategory.SOURCE, 'test action', 'test stage'); test.deepEqual(result.length, 1); test.ok(result[0].match(/may only occur in first stage/), 'the validation should have failed'); test.done(); }, 'cannot be source and is not source'(test: Test) { - const result = validations.validateSourceAction(false, codepipeline.ActionCategory.Deploy, 'test action', 'test stage'); + const result = validations.validateSourceAction(false, codepipeline.ActionCategory.DEPLOY, 'test action', 'test stage'); test.deepEqual(result.length, 0); test.done(); }, diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 4d60cd828b4c5..7bd88176749c6 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnUserPoolClient } from './cognito.generated'; import { IUserPool } from './user-pool'; @@ -9,17 +9,17 @@ export enum AuthFlow { /** * Enable flow for server-side or admin authentication (no client app) */ - AdminNoSrp = 'ADMIN_NO_SRP_AUTH', + ADMIN_NO_SRP = 'ADMIN_NO_SRP_AUTH', /** * Enable custom authentication flow */ - CustomFlowOnly = 'CUSTOM_AUTH_FLOW_ONLY', + CUSTOM_FLOW_ONLY = 'CUSTOM_AUTH_FLOW_ONLY', /** * Enable auth using username & password */ - UserPassword = 'USER_PASSWORD_AUTH' + USER_PASSWORD = 'USER_PASSWORD_AUTH' } export interface UserPoolClientProps { @@ -27,7 +27,7 @@ export interface UserPoolClientProps { * Name of the application client * @default cloudformation generated name */ - readonly clientName?: string; + readonly userPoolClientName?: PhysicalName; /** * The UserPool resource this client will have access to @@ -67,16 +67,18 @@ export class UserPoolClient extends Resource { public readonly userPoolClientClientSecret: string; constructor(scope: Construct, id: string, props: UserPoolClientProps) { - super(scope, id); + super(scope, id, { + physicalName: props.userPoolClientName, + }); const resource = new CfnUserPoolClient(this, 'Resource', { - clientName: props.clientName, + clientName: this.physicalName, generateSecret: props.generateSecret, userPoolId: props.userPool.userPoolId, explicitAuthFlows: props.enabledAuthFlows }); - this.userPoolClientId = resource.refAsString; + this.userPoolClientId = resource.ref; this.userPoolClientClientSecret = resource.attrClientSecret; this.userPoolClientName = resource.attrName; } diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index a02db19683011..409f116ee12ea 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnUserPool } from './cognito.generated'; /** @@ -12,39 +12,39 @@ export enum UserPoolAttribute { /** * End-User's preferred postal address. */ - Address = 'address', + ADDRESS = 'address', /** * End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format. * The year MAY be 0000, indicating that it is omitted. * To represent only the year, YYYY format is allowed. */ - Birthdate = 'birthdate', + BIRTHDATE = 'birthdate', /** * End-User's preferred e-mail address. * Its value MUST conform to the RFC 5322 [RFC5322] addr-spec syntax. */ - Email = 'email', + EMAIL = 'email', /** * Surname(s) or last name(s) of the End-User. * Note that in some cultures, people can have multiple family names or no family name; * all can be present, with the names being separated by space characters. */ - FamilyName = 'family_name', + FAMILY_NAME = 'family_name', /** * End-User's gender. */ - Gender = 'gender', + GENDER = 'gender', /** * Given name(s) or first name(s) of the End-User. * Note that in some cultures, people can have multiple given names; * all can be present, with the names being separated by space characters. */ - GivenName = 'given_name', + GIVEN_NAME = 'given_name', /** * End-User's locale, represented as a BCP47 [RFC5646] language tag. @@ -52,7 +52,7 @@ export enum UserPoolAttribute { * and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. * For example, en-US or fr-CA. */ - Locale = 'locale', + LOCALE = 'locale', /** * Middle name(s) of the End-User. @@ -60,19 +60,19 @@ export enum UserPoolAttribute { * all can be present, with the names being separated by space characters. * Also note that in some cultures, middle names are not used. */ - MiddleName = 'middle_name', + MIDDLE_NAME = 'middle_name', /** * End-User's full name in displayable form including all name parts, * possibly including titles and suffixes, ordered according to the End-User's locale and preferences. */ - Name = 'name', + NAME = 'name', /** * Casual name of the End-User that may or may not be the same as the given_name. * For instance, a nickname value of Mike might be returned alongside a given_name value of Michael. */ - Nickname = 'nickname', + NICKNAME = 'nickname', /** * End-User's preferred telephone number. @@ -80,7 +80,7 @@ export enum UserPoolAttribute { * If the phone number contains an extension, it is RECOMMENDED that the extension be represented using the * RFC 3966 [RFC3966] extension syntax, for example, +1 (604) 555-1234;ext=5678. */ - PhoneNumber = 'phone_number', + PHONE_NUMBER = 'phone_number', /** * URL of the End-User's profile picture. @@ -89,35 +89,35 @@ export enum UserPoolAttribute { * Note that this URL SHOULD specifically reference a profile photo of the End-User * suitable for displaying when describing the End-User, rather than an arbitrary photo taken by the End-User */ - Picture = 'picture', + PICTURE = 'picture', /** * Shorthand name by which the End-User wishes to be referred to. */ - PreferredUsername = 'preferred_username', + PREFERRED_USERNAME = 'preferred_username', /** * URL of the End-User's profile page. The contents of this Web page SHOULD be about the End-User. */ - Profile = 'profile', + PROFILE = 'profile', /** * The End-User's time zone */ - Timezone = 'timezone', + TIMEZONE = 'timezone', /** * Time the End-User's information was last updated. * Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z * as measured in UTC until the date/time. */ - UpdatedAt = 'updated_at', + UPDATED_AT = 'updated_at', /** * URL of the End-User's Web page or blog. * This Web page SHOULD contain information published by the End-User or an organization that the End-User is affiliated with. */ - Website = 'website' + WEBSITE = 'website' } /** @@ -127,22 +127,22 @@ export enum SignInType { /** * End-user will sign in with a username, with optional aliases */ - Username, + USERNAME, /** * End-user will sign in using an email address */ - Email, + EMAIL, /** * End-user will sign in using a phone number */ - Phone, + PHONE, /** * End-user will sign in using either an email address or phone number */ - EmailOrPhone + EMAIL_OR_PHONE } export interface UserPoolTriggers { @@ -204,9 +204,9 @@ export interface UserPoolProps { /** * Name of the user pool * - * @default - Unique ID. + * @default - automatically generated name by CloudFormation at deploy time */ - readonly poolName?: string; + readonly userPoolName?: PhysicalName; /** * Method used for user registration & sign in. @@ -335,42 +335,44 @@ export class UserPool extends Resource implements IUserPool { private triggers: CfnUserPool.LambdaConfigProperty = { }; constructor(scope: Construct, id: string, props: UserPoolProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.userPoolName, + }); let aliasAttributes: UserPoolAttribute[] | undefined; let usernameAttributes: UserPoolAttribute[] | undefined; - if (props.usernameAliasAttributes != null && props.signInType !== SignInType.Username) { + if (props.usernameAliasAttributes != null && props.signInType !== SignInType.USERNAME) { throw new Error(`'usernameAliasAttributes' can only be set with a signInType of 'USERNAME'`); } if (props.usernameAliasAttributes && !props.usernameAliasAttributes.every(a => { - return a === UserPoolAttribute.Email || a === UserPoolAttribute.PhoneNumber || a === UserPoolAttribute.PreferredUsername; + return a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER || a === UserPoolAttribute.PREFERRED_USERNAME; })) { throw new Error(`'usernameAliasAttributes' can only include EMAIL, PHONE_NUMBER, or PREFERRED_USERNAME`); } if (props.autoVerifiedAttributes - && !props.autoVerifiedAttributes.every(a => a === UserPoolAttribute.Email || a === UserPoolAttribute.PhoneNumber)) { + && !props.autoVerifiedAttributes.every(a => a === UserPoolAttribute.EMAIL || a === UserPoolAttribute.PHONE_NUMBER)) { throw new Error(`'autoVerifiedAttributes' can only include EMAIL or PHONE_NUMBER`); } switch (props.signInType) { - case SignInType.Username: + case SignInType.USERNAME: aliasAttributes = props.usernameAliasAttributes; break; - case SignInType.Email: - usernameAttributes = [UserPoolAttribute.Email]; + case SignInType.EMAIL: + usernameAttributes = [UserPoolAttribute.EMAIL]; break; - case SignInType.Phone: - usernameAttributes = [UserPoolAttribute.PhoneNumber]; + case SignInType.PHONE: + usernameAttributes = [UserPoolAttribute.PHONE_NUMBER]; break; - case SignInType.EmailOrPhone: - usernameAttributes = [UserPoolAttribute.Email, UserPoolAttribute.PhoneNumber]; + case SignInType.EMAIL_OR_PHONE: + usernameAttributes = [UserPoolAttribute.EMAIL, UserPoolAttribute.PHONE_NUMBER]; break; default: @@ -389,14 +391,25 @@ export class UserPool extends Resource implements IUserPool { } const userPool = new CfnUserPool(this, 'Resource', { - userPoolName: props.poolName || this.node.uniqueId, + userPoolName: this.physicalName, usernameAttributes, aliasAttributes, autoVerifiedAttributes: props.autoVerifiedAttributes, lambdaConfig: Lazy.anyValue({ produce: () => this.triggers }) }); - this.userPoolId = userPool.refAsString; - this.userPoolArn = userPool.attrArn; + + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: userPool.attrArn, + name: userPool.ref, + arnComponents: { + service: 'cognito', + resource: 'userpool', + resourceName: this.physicalName, + }, + }); + this.userPoolId = resourceIdentifiers.name; + this.userPoolArn = resourceIdentifiers.arn; + this.userPoolProviderName = userPool.attrProviderName; this.userPoolProviderUrl = userPool.attrProviderUrl; } diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 6478bf48ba0b0..baab13d1eb229 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -89,4 +89,4 @@ ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts index d91c9543e133b..5971c7900430d 100644 --- a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts @@ -11,7 +11,7 @@ export = { // WHEN new cognito.UserPool(stack, 'Pool', { - poolName: 'myPool' + userPoolName: cdk.PhysicalName.of('myPool'), }); // THEN @@ -118,8 +118,8 @@ export = { // WHEN new cognito.UserPool(stack, 'Pool', { - signInType: cognito.SignInType.Email, - autoVerifiedAttributes: [cognito.UserPoolAttribute.Email] + signInType: cognito.SignInType.EMAIL, + autoVerifiedAttributes: [cognito.UserPoolAttribute.EMAIL] }); // THEN @@ -138,8 +138,8 @@ export = { // WHEN const toThrow = () => { new cognito.UserPool(stack, 'Pool', { - signInType: cognito.SignInType.Email, - usernameAliasAttributes: [cognito.UserPoolAttribute.PreferredUsername] + signInType: cognito.SignInType.EMAIL, + usernameAliasAttributes: [cognito.UserPoolAttribute.PREFERRED_USERNAME] }); }; @@ -155,8 +155,8 @@ export = { // WHEN const toThrow = () => { new cognito.UserPool(stack, 'Pool', { - signInType: cognito.SignInType.Username, - usernameAliasAttributes: [cognito.UserPoolAttribute.GivenName] + signInType: cognito.SignInType.USERNAME, + usernameAliasAttributes: [cognito.UserPoolAttribute.GIVEN_NAME] }); }; @@ -172,8 +172,8 @@ export = { // WHEN const toThrow = () => { new cognito.UserPool(stack, 'Pool', { - signInType: cognito.SignInType.Email, - autoVerifiedAttributes: [cognito.UserPoolAttribute.Email, cognito.UserPoolAttribute.Gender] + signInType: cognito.SignInType.EMAIL, + autoVerifiedAttributes: [cognito.UserPoolAttribute.EMAIL, cognito.UserPoolAttribute.GENDER] }); }; @@ -181,4 +181,4 @@ export = { test.throws(() => toThrow(), /'autoVerifiedAttributes' can only include EMAIL or PHONE_NUMBER/); test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index 71dbfd50fe14e..343fdb9553162 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); -import { Construct, Lazy, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Lazy, Stack } from '@aws-cdk/cdk'; import { ManagedRule, RuleProps } from './rule'; /** @@ -10,14 +10,14 @@ export interface AccessKeysRotatedProps extends RuleProps { /** * The maximum number of days within which the access keys must be rotated. * - * @default 90 days + * @default Duration.days(90) */ - readonly maxDays?: number; + readonly maxAge?: Duration; } /** * Checks whether the active access keys are rotated within the number of days - * specified in `maxDays`. + * specified in `maxAge`. * * @see https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html * @@ -29,9 +29,9 @@ export class AccessKeysRotated extends ManagedRule { ...props, identifier: 'ACCESS_KEYS_ROTATED', inputParameters: { - ...props.maxDays + ...props.maxAge ? { - maxAccessKeyAge: props.maxDays + maxAccessKeyAge: props.maxAge.toDays() } : {} } diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 8b79ba8979095..4b82fa7923254 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -1,7 +1,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnConfigRule } from './config.generated'; /** @@ -193,7 +193,7 @@ export interface RuleProps { * * @default a CloudFormation generated name */ - readonly name?: string; + readonly configRuleName?: PhysicalName; /** * A description about this AWS Config rule. @@ -248,10 +248,12 @@ export class ManagedRule extends RuleNew { public readonly configRuleComplianceType: string; constructor(scope: Construct, id: string, props: ManagedRuleProps) { - super(scope, id); + super(scope, id, { + physicalName: props.configRuleName, + }); const rule = new CfnConfigRule(this, 'Resource', { - configRuleName: props.name, + configRuleName: this.physicalName, description: props.description, inputParameters: props.inputParameters, maximumExecutionFrequency: props.maximumExecutionFrequency, @@ -262,7 +264,7 @@ export class ManagedRule extends RuleNew { } }); - this.configRuleName = rule.refAsString; + this.configRuleName = rule.ref; this.configRuleArn = rule.attrArn; this.configRuleId = rule.attrConfigRuleId; this.configRuleComplianceType = rule.attrComplianceType; @@ -313,7 +315,9 @@ export class CustomRule extends RuleNew { public readonly configRuleComplianceType: string; constructor(scope: Construct, id: string, props: CustomRuleProps) { - super(scope, id); + super(scope, id, { + physicalName: props.configRuleName, + }); if (!props.configurationChanges && !props.periodic) { throw new Error('At least one of `configurationChanges` or `periodic` must be set to true.'); @@ -354,7 +358,7 @@ export class CustomRule extends RuleNew { this.node.addDependency(props.lambdaFunction); const rule = new CfnConfigRule(this, 'Resource', { - configRuleName: props.name, + configRuleName: this.physicalName, description: props.description, inputParameters: props.inputParameters, maximumExecutionFrequency: props.maximumExecutionFrequency, @@ -366,7 +370,7 @@ export class CustomRule extends RuleNew { } }); - this.configRuleName = rule.refAsString; + this.configRuleName = rule.ref; this.configRuleArn = rule.attrArn; this.configRuleId = rule.attrConfigRuleId; this.configRuleComplianceType = rule.attrComplianceType; diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 0bfe4e1306488..b17d8f5d23335 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -89,4 +89,4 @@ "node": ">= 8.10.0" }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 6434e6b899f49..e460bfe549f77 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -18,7 +18,7 @@ export = { key: 'value' }, maximumExecutionFrequency: config.MaximumExecutionFrequency.THREE_HOURS, - name: 'cool rule' + configRuleName: cdk.PhysicalName.of('cool rule'), }); // THEN @@ -56,7 +56,7 @@ export = { }, lambdaFunction: fn, maximumExecutionFrequency: config.MaximumExecutionFrequency.SIX_HOURS, - name: 'cool rule', + configRuleName: cdk.PhysicalName.of('cool rule'), periodic: true }); diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json index 158553f516be0..b5fe6adad9eec 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-global-lambda-coordinator", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts index 6a865ded03cc8..651f9e55461d7 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts @@ -11,7 +11,7 @@ export interface GlobalTableProps extends cdk.StackProps, dynamodb.TableOptions * Name of the DynamoDB table to use across all regional tables. * This is required for global tables. */ - readonly tableName: string; + readonly tableName: cdk.PhysicalName; /** * Array of environments to create DynamoDB tables in. @@ -39,15 +39,15 @@ export class GlobalTable extends cdk.Construct { super(scope, id); this._regionalTables = []; - if (props.stream != null && props.stream !== dynamodb.StreamViewType.NewAndOldImages) { - throw new Error("dynamoProps.stream MUST be set to dynamodb.StreamViewType.NewAndOldImages"); + if (props.stream != null && props.stream !== dynamodb.StreamViewType.NEW_AND_OLD_IMAGES) { + throw new Error("dynamoProps.stream MUST be set to dynamodb.StreamViewType.NEW_AND_OLD_IMAGES"); } // need to set this stream specification, otherwise global tables don't work // And no way to set a default value in an interface const stackProps: dynamodb.TableProps = { ...props, - stream: dynamodb.StreamViewType.NewAndOldImages + stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES }; // here we loop through the configured regions. diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/global-table-coordinator.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/global-table-coordinator.ts index abcb1b2e0f1c4..8161363dd40aa 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lib/global-table-coordinator.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/global-table-coordinator.ts @@ -17,7 +17,7 @@ export class GlobalTableCoordinator extends cdk.Stack { description: "Lambda to make DynamoDB a global table", handler: "index.handler", runtime: lambda.Runtime.Nodejs810, - timeout: 300, + timeout: cdk.Duration.minutes(5), uuid: "D38B65A6-6B54-4FB6-9BAD-9CD40A6DAC12", }); @@ -28,7 +28,7 @@ export class GlobalTableCoordinator extends cdk.Stack { properties: { regions: props.regions, resourceType: "Custom::DynamoGlobalTableCoordinator", - tableName: props.tableName, + tableName: props.tableName.value, }, }); } diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts index 29dbbd0693823..316b4d2811232 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts @@ -1,12 +1,12 @@ /// !cdk-integ * import { AttributeType } from '@aws-cdk/aws-dynamodb'; -import { App } from '@aws-cdk/cdk'; +import { App, PhysicalName } from '@aws-cdk/cdk'; import { GlobalTable } from '../lib'; const app = new App(); new GlobalTable(app, 'globdynamodbinteg', { - partitionKey: { name: 'hashKey', type: AttributeType.String }, - tableName: 'integrationtest', + partitionKey: { name: 'hashKey', type: AttributeType.STRING }, + tableName: PhysicalName.of('integrationtest'), regions: [ "us-east-1", "us-east-2", "us-west-2" ] }); app.synth(); diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts index 56ea1876f3442..e73f7bb5dfb36 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts +++ b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Attribute, AttributeType, StreamViewType } from '@aws-cdk/aws-dynamodb'; import { Table } from '@aws-cdk/aws-dynamodb'; -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import * as assert from 'assert'; import { Test } from 'nodeunit'; import { @@ -16,11 +16,11 @@ const CONSTRUCT_NAME = 'aws-cdk-dynamodb-global'; // DynamoDB table parameters const TABLE_NAME = 'GlobalTable'; -const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.String }; +const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.STRING }; const STACK_PROPS: GlobalTableProps = { partitionKey: TABLE_PARTITION_KEY, - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] }; @@ -60,7 +60,7 @@ export = { expect(customResourceStack).to(haveResource('AWS::CloudFormation::CustomResource', { Regions: STACK_PROPS.regions, ResourceType: "Custom::DynamoGlobalTableCoordinator", - TableName: STACK_PROPS.tableName + TableName: TABLE_NAME, })); test.done(); }, @@ -70,8 +70,8 @@ export = { const stack = new Stack(); try { new GlobalTable(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, - stream: StreamViewType.KeysOnly, + tableName: PhysicalName.of(TABLE_NAME), + stream: StreamViewType.KEYS_ONLY, partitionKey: TABLE_PARTITION_KEY, regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] }); @@ -89,7 +89,7 @@ export = { 'global dynamo should only allow NEW_AND_OLD_IMAGES'(test: Test) { const stack = new Stack(); const regTables = new GlobalTable(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), partitionKey: TABLE_PARTITION_KEY, regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] }); diff --git a/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts b/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts index a0fb5e83bf946..2d6c504d584d8 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/scalable-table-attribute.ts @@ -21,14 +21,14 @@ export class ScalableTableAttribute extends appscaling.BaseScalableAttribute { throw new RangeError(`targetUtilizationPercent for DynamoDB scaling must be between 10 and 90 percent, got: ${props.targetUtilizationPercent}`); } const predefinedMetric = this.props.dimension.indexOf('ReadCapacity') === -1 - ? appscaling.PredefinedMetric.DynamoDBWriteCapacityUtilization - : appscaling.PredefinedMetric.DynamoDBReadCapacityUtilization; + ? appscaling.PredefinedMetric.DYANMODB_WRITE_CAPACITY_UTILIZATION + : appscaling.PredefinedMetric.DYNAMODB_READ_CAPACITY_UTILIZATION; super.doScaleToTrackMetric('Tracking', { policyName: props.policyName, disableScaleIn: props.disableScaleIn, - scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec, + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown, targetValue: props.targetUtilizationPercent, predefinedMetric, }); diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 0b91f28c26cdc..0c1178e9a1f40 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1,6 +1,6 @@ import appscaling = require('@aws-cdk/aws-applicationautoscaling'); import iam = require('@aws-cdk/aws-iam'); -import { Aws, Construct, Lazy, Resource, Stack } from '@aws-cdk/cdk'; +import { Aws, Construct, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnTable } from './dynamodb.generated'; import { EnableScalingProps, IScalableTableAttribute } from './scalable-attribute-api'; import { ScalableTableAttribute } from './scalable-table-attribute'; @@ -114,7 +114,7 @@ export interface TableProps extends TableOptions { * Enforces a particular physical table name. * @default */ - readonly tableName?: string; + readonly tableName?: PhysicalName; } export interface SecondaryIndexProps { @@ -224,20 +224,22 @@ export class Table extends Resource { private readonly scalingRole: iam.IRole; constructor(scope: Construct, id: string, props: TableProps) { - super(scope, id); + super(scope, id, { + physicalName: props.tableName, + }); - this.billingMode = props.billingMode || BillingMode.Provisioned; + this.billingMode = props.billingMode || BillingMode.PROVISIONED; this.validateProvisioning(props); this.table = new CfnTable(this, 'Resource', { - tableName: props.tableName, + tableName: this.physicalName, keySchema: this.keySchema, attributeDefinitions: this.attributeDefinitions, globalSecondaryIndexes: Lazy.anyValue({ produce: () => this.globalSecondaryIndexes }, { omitEmptyArray: true }), localSecondaryIndexes: Lazy.anyValue({ produce: () => this.localSecondaryIndexes }, { omitEmptyArray: true }), pointInTimeRecoverySpecification: props.pointInTimeRecovery ? { pointInTimeRecoveryEnabled: props.pointInTimeRecovery } : undefined, - billingMode: this.billingMode === BillingMode.PayPerRequest ? this.billingMode : undefined, - provisionedThroughput: props.billingMode === BillingMode.PayPerRequest ? undefined : { + billingMode: this.billingMode === BillingMode.PAY_PER_REQUEST ? this.billingMode : undefined, + provisionedThroughput: props.billingMode === BillingMode.PAY_PER_REQUEST ? undefined : { readCapacityUnits: props.readCapacity || 5, writeCapacityUnits: props.writeCapacity || 5 }, @@ -248,8 +250,18 @@ export class Table extends Resource { if (props.tableName) { this.node.addMetadata('aws:cdk:hasPhysicalName', props.tableName); } - this.tableArn = this.table.attrArn; - this.tableName = this.table.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: this.table.attrArn, + name: this.table.ref, + arnComponents: { + service: 'dynamodb', + resource: 'table', + resourceName: this.physicalName, + }, + }); + this.tableArn = resourceIdentifiers.arn; + this.tableName = resourceIdentifiers.name; + this.tableStreamArn = this.table.attrStreamArn; this.scalingRole = this.makeScalingRole(); @@ -281,7 +293,7 @@ export class Table extends Resource { indexName: props.indexName, keySchema: gsiKeySchema, projection: gsiProjection, - provisionedThroughput: this.billingMode === BillingMode.PayPerRequest ? undefined : { + provisionedThroughput: this.billingMode === BillingMode.PAY_PER_REQUEST ? undefined : { readCapacityUnits: props.readCapacity || 5, writeCapacityUnits: props.writeCapacity || 5 } @@ -324,12 +336,12 @@ export class Table extends Resource { if (this.tableScaling.scalableReadAttribute) { throw new Error('Read AutoScaling already enabled for this table'); } - if (this.billingMode === BillingMode.PayPerRequest) { + if (this.billingMode === BillingMode.PAY_PER_REQUEST) { throw new Error('AutoScaling is not available for tables with PAY_PER_REQUEST billing mode'); } return this.tableScaling.scalableReadAttribute = new ScalableTableAttribute(this, 'ReadScaling', { - serviceNamespace: appscaling.ServiceNamespace.DynamoDb, + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, resourceId: `table/${this.tableName}`, dimension: 'dynamodb:table:ReadCapacityUnits', role: this.scalingRole, @@ -346,12 +358,12 @@ export class Table extends Resource { if (this.tableScaling.scalableWriteAttribute) { throw new Error('Write AutoScaling already enabled for this table'); } - if (this.billingMode === BillingMode.PayPerRequest) { + if (this.billingMode === BillingMode.PAY_PER_REQUEST) { throw new Error('AutoScaling is not available for tables with PAY_PER_REQUEST billing mode'); } return this.tableScaling.scalableWriteAttribute = new ScalableTableAttribute(this, 'WriteScaling', { - serviceNamespace: appscaling.ServiceNamespace.DynamoDb, + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, resourceId: `table/${this.tableName}`, dimension: 'dynamodb:table:WriteCapacityUnits', role: this.scalingRole, @@ -365,7 +377,7 @@ export class Table extends Resource { * @returns An object to configure additional AutoScaling settings for this attribute */ public autoScaleGlobalSecondaryIndexReadCapacity(indexName: string, props: EnableScalingProps): IScalableTableAttribute { - if (this.billingMode === BillingMode.PayPerRequest) { + if (this.billingMode === BillingMode.PAY_PER_REQUEST) { throw new Error('AutoScaling is not available for tables with PAY_PER_REQUEST billing mode'); } const attributePair = this.indexScaling.get(indexName); @@ -377,7 +389,7 @@ export class Table extends Resource { } return attributePair.scalableReadAttribute = new ScalableTableAttribute(this, `${indexName}ReadScaling`, { - serviceNamespace: appscaling.ServiceNamespace.DynamoDb, + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, resourceId: `table/${this.tableName}/index/${indexName}`, dimension: 'dynamodb:index:ReadCapacityUnits', role: this.scalingRole, @@ -391,7 +403,7 @@ export class Table extends Resource { * @returns An object to configure additional AutoScaling settings for this attribute */ public autoScaleGlobalSecondaryIndexWriteCapacity(indexName: string, props: EnableScalingProps): IScalableTableAttribute { - if (this.billingMode === BillingMode.PayPerRequest) { + if (this.billingMode === BillingMode.PAY_PER_REQUEST) { throw new Error('AutoScaling is not available for tables with PAY_PER_REQUEST billing mode'); } const attributePair = this.indexScaling.get(indexName); @@ -403,7 +415,7 @@ export class Table extends Resource { } return attributePair.scalableWriteAttribute = new ScalableTableAttribute(this, `${indexName}WriteScaling`, { - serviceNamespace: appscaling.ServiceNamespace.DynamoDb, + serviceNamespace: appscaling.ServiceNamespace.DYNAMODB, resourceId: `table/${this.tableName}/index/${indexName}`, dimension: 'dynamodb:index:WriteCapacityUnits', role: this.scalingRole, @@ -514,7 +526,7 @@ export class Table extends Resource { * @param props read and write capacity properties */ private validateProvisioning(props: { readCapacity?: number, writeCapacity?: number}): void { - if (this.billingMode === BillingMode.PayPerRequest) { + if (this.billingMode === BillingMode.PAY_PER_REQUEST) { if (props.readCapacity !== undefined || props.writeCapacity !== undefined) { throw new Error('you cannot provision read and write capacity for a table with PAY_PER_REQUEST billing mode'); } @@ -572,14 +584,14 @@ export class Table extends Resource { } private buildIndexProjection(props: SecondaryIndexProps): CfnTable.ProjectionProperty { - if (props.projectionType === ProjectionType.Include && !props.nonKeyAttributes) { + if (props.projectionType === ProjectionType.INCLUDE && !props.nonKeyAttributes) { // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-projectionobject.html - throw new Error(`non-key attributes should be specified when using ${ProjectionType.Include} projection type`); + throw new Error(`non-key attributes should be specified when using ${ProjectionType.INCLUDE} projection type`); } - if (props.projectionType !== ProjectionType.Include && props.nonKeyAttributes) { + if (props.projectionType !== ProjectionType.INCLUDE && props.nonKeyAttributes) { // this combination causes validation exception, status code 400, while trying to create CFN stack - throw new Error(`non-key attributes should not be specified when not using ${ProjectionType.Include} projection type`); + throw new Error(`non-key attributes should not be specified when not using ${ProjectionType.INCLUDE} projection type`); } if (props.nonKeyAttributes) { @@ -587,7 +599,7 @@ export class Table extends Resource { } return { - projectionType: props.projectionType ? props.projectionType : ProjectionType.All, + projectionType: props.projectionType ? props.projectionType : ProjectionType.ALL, nonKeyAttributes: props.nonKeyAttributes ? props.nonKeyAttributes : undefined }; } @@ -651,9 +663,9 @@ export class Table extends Resource { } export enum AttributeType { - Binary = 'B', - Number = 'N', - String = 'S', + BINARY = 'B', + NUMBER = 'N', + STRING = 'S', } /** @@ -663,17 +675,17 @@ export enum BillingMode { /** * Pay only for what you use. You don't configure Read/Write capacity units. */ - PayPerRequest = 'PAY_PER_REQUEST', + PAY_PER_REQUEST = 'PAY_PER_REQUEST', /** * Explicitly specified Read/Write capacity units. */ - Provisioned = 'PROVISIONED', + PROVISIONED = 'PROVISIONED', } export enum ProjectionType { - KeysOnly = 'KEYS_ONLY', - Include = 'INCLUDE', - All = 'ALL' + KEYS_ONLY = 'KEYS_ONLY', + INCLUDE = 'INCLUDE', + ALL = 'ALL' } /** @@ -684,13 +696,13 @@ export enum ProjectionType { */ export enum StreamViewType { /** The entire item, as it appears after it was modified, is written to the stream. */ - NewImage = 'NEW_IMAGE', + NEW_IMAGE = 'NEW_IMAGE', /** The entire item, as it appeared before it was modified, is written to the stream. */ - OldImage = 'OLD_IMAGE', + OLD_IMAGE = 'OLD_IMAGE', /** Both the new and the old item images of the item are written to the stream. */ - NewAndOldImages = 'NEW_AND_OLD_IMAGES', + NEW_AND_OLD_IMAGES = 'NEW_AND_OLD_IMAGES', /** Only the key attributes of the modified item are written to the stream. */ - KeysOnly = 'KEYS_ONLY' + KEYS_ONLY = 'KEYS_ONLY' } /** diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.ts index 7173832bfbde7..98b6a7fc8d3f6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.ts @@ -6,7 +6,7 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-dynamodb'); const table = new dynamodb.Table(stack, 'Table', { - partitionKey: { name: 'hashKey', type: dynamodb.AttributeType.String } + partitionKey: { name: 'hashKey', type: dynamodb.AttributeType.STRING } }); /// !show diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts index 38514f6e93a2c..4ff942c7de643 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ondemand.ts @@ -9,8 +9,8 @@ const TABLE = 'Table'; const TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX = 'TableWithGlobalAndLocalSecondaryIndex'; const TABLE_WITH_GLOBAL_SECONDARY_INDEX = 'TableWithGlobalSecondaryIndex'; const TABLE_WITH_LOCAL_SECONDARY_INDEX = 'TableWithLocalSecondaryIndex'; -const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.String }; -const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.Number }; +const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.STRING }; +const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.NUMBER }; // DynamoDB global secondary index parameters const GSI_TEST_CASE_1 = 'GSI-PartitionKeyOnly'; @@ -18,8 +18,8 @@ const GSI_TEST_CASE_2 = 'GSI-PartitionAndSortKeyWithReadAndWriteCapacity'; const GSI_TEST_CASE_3 = 'GSI-ProjectionTypeKeysOnly'; const GSI_TEST_CASE_4 = 'GSI-ProjectionTypeInclude'; const GSI_TEST_CASE_5 = 'GSI-InverseTableKeySchema'; -const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.String }; -const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.Number }; +const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.STRING }; +const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.NUMBER }; const GSI_NON_KEY: string[] = []; for (let i = 0; i < 10; i++) { // 'A' to 'J' GSI_NON_KEY.push(String.fromCharCode(65 + i)); @@ -30,7 +30,7 @@ const LSI_TEST_CASE_1 = 'LSI-PartitionAndSortKey'; const LSI_TEST_CASE_2 = 'LSI-PartitionAndTableSortKey'; const LSI_TEST_CASE_3 = 'LSI-ProjectionTypeKeysOnly'; const LSI_TEST_CASE_4 = 'LSI-ProjectionTypeInclude'; -const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.Number }; +const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.NUMBER }; const LSI_NON_KEY: string[] = []; for (let i = 0; i < 10; i++) { // 'K' to 'T' LSI_NON_KEY.push(String.fromCharCode(75 + i)); @@ -42,15 +42,15 @@ const stack = new Stack(app, STACK_NAME); // Provisioned tables new Table(stack, TABLE, { - billingMode: BillingMode.PayPerRequest, + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY }); const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, serverSideEncryption: true, - stream: StreamViewType.KeysOnly, - billingMode: BillingMode.PayPerRequest, + stream: StreamViewType.KEYS_ONLY, + billingMode: BillingMode.PAY_PER_REQUEST, timeToLiveAttribute: 'timeToLive', partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY @@ -71,13 +71,13 @@ tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_3, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly, + projectionType: ProjectionType.KEYS_ONLY, }); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_4, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: GSI_NON_KEY }); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ @@ -97,17 +97,17 @@ tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ indexName: LSI_TEST_CASE_3, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly + projectionType: ProjectionType.KEYS_ONLY }); tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ indexName: LSI_TEST_CASE_4, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: LSI_NON_KEY }); const tableWithGlobalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_SECONDARY_INDEX, { - billingMode: BillingMode.PayPerRequest, + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, }); tableWithGlobalSecondaryIndex.addGlobalSecondaryIndex({ @@ -116,7 +116,7 @@ tableWithGlobalSecondaryIndex.addGlobalSecondaryIndex({ }); const tableWithLocalSecondaryIndex = new Table(stack, TABLE_WITH_LOCAL_SECONDARY_INDEX, { - billingMode: BillingMode.PayPerRequest, + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY, }); diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts index 62b135a71152a..2281996c14871 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts @@ -10,8 +10,8 @@ const TABLE = 'Table'; const TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX = 'TableWithGlobalAndLocalSecondaryIndex'; const TABLE_WITH_GLOBAL_SECONDARY_INDEX = 'TableWithGlobalSecondaryIndex'; const TABLE_WITH_LOCAL_SECONDARY_INDEX = 'TableWithLocalSecondaryIndex'; -const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.String }; -const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.Number }; +const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.STRING }; +const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.NUMBER }; // DynamoDB global secondary index parameters const GSI_TEST_CASE_1 = 'GSI-PartitionKeyOnly'; @@ -19,8 +19,8 @@ const GSI_TEST_CASE_2 = 'GSI-PartitionAndSortKeyWithReadAndWriteCapacity'; const GSI_TEST_CASE_3 = 'GSI-ProjectionTypeKeysOnly'; const GSI_TEST_CASE_4 = 'GSI-ProjectionTypeInclude'; const GSI_TEST_CASE_5 = 'GSI-InverseTableKeySchema'; -const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.String }; -const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.Number }; +const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.STRING }; +const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.NUMBER }; const GSI_NON_KEY: string[] = []; for (let i = 0; i < 10; i++) { // 'A' to 'J' GSI_NON_KEY.push(String.fromCharCode(65 + i)); @@ -31,7 +31,7 @@ const LSI_TEST_CASE_1 = 'LSI-PartitionAndSortKey'; const LSI_TEST_CASE_2 = 'LSI-PartitionAndTableSortKey'; const LSI_TEST_CASE_3 = 'LSI-ProjectionTypeKeysOnly'; const LSI_TEST_CASE_4 = 'LSI-ProjectionTypeInclude'; -const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.Number }; +const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.NUMBER }; const LSI_NON_KEY: string[] = []; for (let i = 0; i < 10; i++) { // 'K' to 'T' LSI_NON_KEY.push(String.fromCharCode(75 + i)); @@ -48,7 +48,7 @@ const table = new Table(stack, TABLE, { const tableWithGlobalAndLocalSecondaryIndex = new Table(stack, TABLE_WITH_GLOBAL_AND_LOCAL_SECONDARY_INDEX, { pointInTimeRecovery: true, serverSideEncryption: true, - stream: StreamViewType.KeysOnly, + stream: StreamViewType.KEYS_ONLY, timeToLiveAttribute: 'timeToLive', partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY @@ -70,13 +70,13 @@ tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_3, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly, + projectionType: ProjectionType.KEYS_ONLY, }); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ indexName: GSI_TEST_CASE_4, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: GSI_NON_KEY }); tableWithGlobalAndLocalSecondaryIndex.addGlobalSecondaryIndex({ @@ -96,12 +96,12 @@ tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ indexName: LSI_TEST_CASE_3, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly + projectionType: ProjectionType.KEYS_ONLY }); tableWithGlobalAndLocalSecondaryIndex.addLocalSecondaryIndex({ indexName: LSI_TEST_CASE_4, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: LSI_NON_KEY }); diff --git a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts index 2faf49cd60a63..fc2bb3efce1b6 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import appscaling = require('@aws-cdk/aws-applicationautoscaling'); import iam = require('@aws-cdk/aws-iam'); -import { ConstructNode, Stack, Tag } from '@aws-cdk/cdk'; +import { ConstructNode, PhysicalName, Stack, Tag } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Attribute, @@ -21,13 +21,13 @@ const CONSTRUCT_NAME = 'MyTable'; // DynamoDB table parameters const TABLE_NAME = 'MyTable'; -const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.String }; -const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.Number }; +const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.STRING }; +const TABLE_SORT_KEY: Attribute = { name: 'sortKey', type: AttributeType.NUMBER }; // DynamoDB global secondary index parameters const GSI_NAME = 'MyGSI'; -const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.String }; -const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.Binary }; +const GSI_PARTITION_KEY: Attribute = { name: 'gsiHashKey', type: AttributeType.STRING }; +const GSI_SORT_KEY: Attribute = { name: 'gsiSortKey', type: AttributeType.BINARY }; const GSI_NON_KEY = 'gsiNonKey'; function* GSI_GENERATOR() { let n = 0; @@ -50,7 +50,7 @@ function* NON_KEY_ATTRIBUTE_GENERATOR(nonKeyPrefix: string) { // DynamoDB local secondary index parameters const LSI_NAME = 'MyLSI'; -const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.Number }; +const LSI_SORT_KEY: Attribute = { name: 'lsiSortKey', type: AttributeType.NUMBER }; const LSI_NON_KEY = 'lsiNonKey'; function* LSI_GENERATOR() { let n = 0; @@ -219,10 +219,10 @@ export = { const stack = new Stack(); new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), readCapacity: 42, writeCapacity: 1337, - stream: StreamViewType.NewAndOldImages, + stream: StreamViewType.NEW_AND_OLD_IMAGES, partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }); @@ -249,10 +249,10 @@ export = { const stack = new Stack(); new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), readCapacity: 42, writeCapacity: 1337, - stream: StreamViewType.NewImage, + stream: StreamViewType.NEW_IMAGE, partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }); @@ -279,10 +279,10 @@ export = { const stack = new Stack(); new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), readCapacity: 42, writeCapacity: 1337, - stream: StreamViewType.OldImage, + stream: StreamViewType.OLD_IMAGE, partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }); @@ -309,13 +309,13 @@ export = { 'when specifying every property'(test: Test) { const stack = new Stack(); const table = new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, + tableName: PhysicalName.of(TABLE_NAME), readCapacity: 42, writeCapacity: 1337, pointInTimeRecovery: true, serverSideEncryption: true, - billingMode: BillingMode.Provisioned, - stream: StreamViewType.KeysOnly, + billingMode: BillingMode.PROVISIONED, + stream: StreamViewType.KEYS_ONLY, timeToLiveAttribute: 'timeToLive', partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY, @@ -350,8 +350,8 @@ export = { 'when specifying PAY_PER_REQUEST billing mode'(test: Test) { const stack = new Stack(); new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, - billingMode: BillingMode.PayPerRequest, + tableName: PhysicalName.of(TABLE_NAME), + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY }); @@ -373,20 +373,20 @@ export = { 'error when specifying read or write capacity with a PAY_PER_REQUEST billing mode'(test: Test) { const stack = new Stack(); test.throws(() => new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, - billingMode: BillingMode.PayPerRequest, + tableName: PhysicalName.of(TABLE_NAME), + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, readCapacity: 1 })); test.throws(() => new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, - billingMode: BillingMode.PayPerRequest, + tableName: PhysicalName.of(TABLE_NAME), + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, writeCapacity: 1 })); test.throws(() => new Table(stack, CONSTRUCT_NAME, { - tableName: TABLE_NAME, - billingMode: BillingMode.PayPerRequest, + tableName: PhysicalName.of(TABLE_NAME), + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, readCapacity: 1, writeCapacity: 1 @@ -447,7 +447,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.All, + projectionType: ProjectionType.ALL, readCapacity: 42, writeCapacity: 1337 }); @@ -492,7 +492,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly, + projectionType: ProjectionType.KEYS_ONLY, }); expect(stack).to(haveResource('AWS::DynamoDB::Table', @@ -532,7 +532,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: [gsiNonKeyAttributeGenerator.next().value, gsiNonKeyAttributeGenerator.next().value], readCapacity: 42, writeCapacity: 1337 @@ -570,7 +570,7 @@ export = { 'when adding a global secondary index on a table with PAY_PER_REQUEST billing mode'(test: Test) { const stack = new Stack(); new Table(stack, CONSTRUCT_NAME, { - billingMode: BillingMode.PayPerRequest, + billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }).addGlobalSecondaryIndex({ @@ -611,7 +611,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include + projectionType: ProjectionType.INCLUDE }), /non-key attributes should be specified when using INCLUDE projection type/); test.done(); @@ -639,7 +639,7 @@ export = { test.throws(() => table.addGlobalSecondaryIndex({ indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, - projectionType: ProjectionType.KeysOnly, + projectionType: ProjectionType.KEYS_ONLY, nonKeyAttributes: [gsiNonKeyAttributeGenerator.next().value] }), /non-key attributes should not be specified when not using INCLUDE projection type/); @@ -659,7 +659,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: gsiNonKeyAttributes }), /a maximum number of nonKeyAttributes across all of secondary indexes is 20/); @@ -674,7 +674,7 @@ export = { indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY, sortKey: GSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: [GSI_NON_KEY, TABLE_PARTITION_KEY.name] // tslint:disable-next-line:max-line-length }), /a key attribute, hashKey, is part of a list of non-key attributes, gsiNonKey,hashKey, which is not allowed since all key attributes are added automatically and this configuration causes stack creation failure/); @@ -686,7 +686,7 @@ export = { const stack = new Stack(); const table = new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, - billingMode: BillingMode.PayPerRequest + billingMode: BillingMode.PAY_PER_REQUEST }); test.throws(() => table.addGlobalSecondaryIndex({ @@ -861,7 +861,7 @@ export = { table.addLocalSecondaryIndex({ indexName: LSI_NAME, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.KeysOnly + projectionType: ProjectionType.KEYS_ONLY }); expect(stack).to(haveResource('AWS::DynamoDB::Table', @@ -898,7 +898,7 @@ export = { table.addLocalSecondaryIndex({ indexName: LSI_NAME, sortKey: LSI_SORT_KEY, - projectionType: ProjectionType.Include, + projectionType: ProjectionType.INCLUDE, nonKeyAttributes: [ lsiNonKeyAttributeGenerator.next().value, lsiNonKeyAttributeGenerator.next().value ] }); @@ -1045,7 +1045,7 @@ export = { 'error when enabling AutoScaling on the PAY_PER_REQUEST table'(test: Test) { // GIVEN const stack = new Stack(); - const table = new Table(stack, CONSTRUCT_NAME, { billingMode: BillingMode.PayPerRequest, partitionKey: TABLE_PARTITION_KEY }); + const table = new Table(stack, CONSTRUCT_NAME, { billingMode: BillingMode.PAY_PER_REQUEST, partitionKey: TABLE_PARTITION_KEY }); table.addGlobalSecondaryIndex({ indexName: GSI_NAME, partitionKey: GSI_PARTITION_KEY @@ -1096,7 +1096,7 @@ export = { const table = new Table(stack, CONSTRUCT_NAME, { readCapacity: 42, writeCapacity: 1337, - partitionKey: { name: 'Hash', type: AttributeType.String } + partitionKey: { name: 'Hash', type: AttributeType.STRING } }); // WHEN @@ -1178,9 +1178,9 @@ export = { const table = new Table(stack, 'my-table', { partitionKey: { name: 'id', - type: AttributeType.String + type: AttributeType.STRING }, - stream: StreamViewType.NewImage + stream: StreamViewType.NEW_IMAGE }); const user = new iam.User(stack, 'user'); @@ -1216,8 +1216,8 @@ export = { // GIVEN const stack = new Stack(); - const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.String } }); - table.addGlobalSecondaryIndex({ indexName: 'MyIndex', partitionKey: { name: 'Age', type: AttributeType.Number }}); + const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.STRING } }); + table.addGlobalSecondaryIndex({ indexName: 'MyIndex', partitionKey: { name: 'Age', type: AttributeType.NUMBER }}); const user = new iam.User(stack, 'user'); // WHEN @@ -1255,7 +1255,7 @@ export = { function testGrant(test: Test, expectedActions: string[], invocation: (user: iam.IPrincipal, table: Table) => void) { // GIVEN const stack = new Stack(); - const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.String } }); + const table = new Table(stack, 'my-table', { partitionKey: { name: 'ID', type: AttributeType.STRING } }); const user = new iam.User(stack, 'user'); // WHEN diff --git a/packages/@aws-cdk/aws-ec2/lib/connections.ts b/packages/@aws-cdk/aws-ec2/lib/connections.ts index 72854790af010..dc9013a5f0c40 100644 --- a/packages/@aws-cdk/aws-ec2/lib/connections.ts +++ b/packages/@aws-cdk/aws-ec2/lib/connections.ts @@ -1,5 +1,6 @@ +import { IPeer, Peer } from "./peer"; +import { Port } from './port'; import { ISecurityGroup } from "./security-group"; -import { AnyIPv4, IPortRange, ISecurityGroupRule } from "./security-group-rule"; /** * The goal of this module is to make possible to write statements like this: @@ -33,7 +34,7 @@ export interface ConnectionsProps { * * @default Derived from securityGroup if set. */ - readonly securityGroupRule?: ISecurityGroupRule; + readonly peer?: IPeer; /** * What securityGroup(s) this object is managing connections for @@ -45,9 +46,9 @@ export interface ConnectionsProps { /** * Default port range for initiating connections to and from this object * - * @default No default port range + * @default - No default port */ - readonly defaultPortRange?: IPortRange; + readonly defaultPort?: Port; } /** @@ -67,7 +68,7 @@ export class Connections implements IConnectable { /** * The default port configured for this connection peer, if available */ - public readonly defaultPortRange?: IPortRange; + public readonly defaultPort?: Port; /** * Underlying securityGroup for this Connections object, if present @@ -80,7 +81,7 @@ export class Connections implements IConnectable { /** * The rule that defines how to represent this peer in a security group */ - private readonly _securityGroupRules = new ReactiveList(); + private readonly _securityGroupRules = new ReactiveList(); /** * When doing bidirectional grants between Connections, make sure we don't recursive infinitely @@ -97,11 +98,11 @@ export class Connections implements IConnectable { this._securityGroups.push(...(props.securityGroups || [])); this._securityGroupRules.push(...this._securityGroups.asArray()); - if (props.securityGroupRule) { - this._securityGroupRules.push(props.securityGroupRule); + if (props.peer) { + this._securityGroupRules.push(props.peer); } - this.defaultPortRange = props.defaultPortRange; + this.defaultPort = props.defaultPort; } public get securityGroups(): ISecurityGroup[] { @@ -121,7 +122,7 @@ export class Connections implements IConnectable { /** * Allow connections to the peer on the given port */ - public allowTo(other: IConnectable, portRange: IPortRange, description?: string) { + public allowTo(other: IConnectable, portRange: Port, description?: string) { if (this.skip) { return; } const remoteRule = this.remoteRule; // Capture current value into local for callback to close over @@ -144,7 +145,7 @@ export class Connections implements IConnectable { /** * Allow connections from the peer on the given port */ - public allowFrom(other: IConnectable, portRange: IPortRange, description?: string) { + public allowFrom(other: IConnectable, portRange: Port, description?: string) { if (this.skip) { return; } const remoteRule = this.remoteRule; // Capture current value into local for callback to close over @@ -167,7 +168,7 @@ export class Connections implements IConnectable { /** * Allow hosts inside the security group to connect to each other on the given port */ - public allowInternally(portRange: IPortRange, description?: string) { + public allowInternally(portRange: Port, description?: string) { this._securityGroups.forEachAndForever(securityGroup => { this._securityGroupRules.forEachAndForever(rule => { securityGroup.addIngressRule(rule, portRange, description); @@ -180,15 +181,15 @@ export class Connections implements IConnectable { /** * Allow to all IPv4 ranges */ - public allowToAnyIPv4(portRange: IPortRange, description?: string) { - this.allowTo(new AnyIPv4(), portRange, description); + public allowToAnyIPv4(portRange: Port, description?: string) { + this.allowTo(Peer.anyIpv4(), portRange, description); } /** * Allow from any IPv4 ranges */ - public allowFromAnyIPv4(portRange: IPortRange, description?: string) { - this.allowFrom(new AnyIPv4(), portRange, description); + public allowFromAnyIPv4(portRange: Port, description?: string) { + this.allowFrom(Peer.anyIpv4(), portRange, description); } /** @@ -197,41 +198,41 @@ export class Connections implements IConnectable { * Even if the peer has a default port, we will always use our default port. */ public allowDefaultPortFrom(other: IConnectable, description?: string) { - if (!this.defaultPortRange) { + if (!this.defaultPort) { throw new Error('Cannot call allowDefaultPortFrom(): this resource has no default port'); } - this.allowFrom(other, this.defaultPortRange, description); + this.allowFrom(other, this.defaultPort, description); } /** * Allow hosts inside the security group to connect to each other */ public allowDefaultPortInternally(description?: string) { - if (!this.defaultPortRange) { + if (!this.defaultPort) { throw new Error('Cannot call allowDefaultPortInternally(): this resource has no default port'); } - this.allowInternally(this.defaultPortRange, description); + this.allowInternally(this.defaultPort, description); } /** * Allow default connections from all IPv4 ranges */ public allowDefaultPortFromAnyIpv4(description?: string) { - if (!this.defaultPortRange) { + if (!this.defaultPort) { throw new Error('Cannot call allowDefaultPortFromAnyIpv4(): this resource has no default port'); } - this.allowFromAnyIPv4(this.defaultPortRange, description); + this.allowFromAnyIPv4(this.defaultPort, description); } /** * Allow connections to the security group on their default port */ public allowToDefaultPort(other: IConnectable, description?: string) { - if (other.connections.defaultPortRange === undefined) { + if (other.connections.defaultPort === undefined) { throw new Error('Cannot call alloToDefaultPort(): other resource has no default port'); } - this.allowTo(other, other.connections.defaultPortRange, description); + this.allowTo(other, other.connections.defaultPort, description); } /** @@ -240,10 +241,10 @@ export class Connections implements IConnectable { * Even if the peer has a default port, we will always use our default port. */ public allowDefaultPortTo(other: IConnectable, description?: string) { - if (!this.defaultPortRange) { + if (!this.defaultPort) { throw new Error('Cannot call allowDefaultPortTo(): this resource has no default port'); } - this.allowTo(other, this.defaultPortRange, description); + this.allowTo(other, this.defaultPort, description); } } diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index 4bbfedd98c42e..4c9337e333d8f 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -1,12 +1,15 @@ export * from './connections'; export * from './instance-types'; export * from './machine-image'; +export * from './port'; export * from './security-group'; -export * from './security-group-rule'; +export * from './peer'; export * from './vpc'; export * from './vpc-lookup'; export * from './vpn'; export * from './vpc-endpoint'; +export * from './user-data'; +export * from './windows-versions'; // AWS::EC2 CloudFormation Resources: export * from './ec2.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index f235f2a7ebb9a..77530875d53b9 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -11,7 +11,7 @@ export enum InstanceClass { /** * Standard instances, 3rd generation */ - Standard3 = 'm3', + STANDARD3 = 'm3', /** * Standard instances, 3rd generation @@ -21,7 +21,7 @@ export enum InstanceClass { /** * Standard instances, 4th generation */ - Standard4 = 'm4', + STANDARD4 = 'm4', /** * Standard instances, 4th generation @@ -31,7 +31,7 @@ export enum InstanceClass { /** * Standard instances, 5th generation */ - Standard5 = 'm5', + STANDARD5 = 'm5', /** * Standard instances, 5th generation @@ -41,7 +41,7 @@ export enum InstanceClass { /** * Memory optimized instances, 3rd generation */ - Memory3 = 'r3', + MEMORY3 = 'r3', /** * Memory optimized instances, 3rd generation @@ -51,7 +51,7 @@ export enum InstanceClass { /** * Memory optimized instances, 3rd generation */ - Memory4 = 'r4', + MEMORY4 = 'r4', /** * Memory optimized instances, 3rd generation @@ -61,7 +61,7 @@ export enum InstanceClass { /** * Compute optimized instances, 3rd generation */ - Compute3 = 'c3', + COMPUTE3 = 'c3', /** * Compute optimized instances, 3rd generation @@ -71,7 +71,7 @@ export enum InstanceClass { /** * Compute optimized instances, 4th generation */ - Compute4 = 'c4', + COMPUTE4 = 'c4', /** * Compute optimized instances, 4th generation @@ -81,7 +81,7 @@ export enum InstanceClass { /** * Compute optimized instances, 5th generation */ - Compute5 = 'c5', + COMPUTE5 = 'c5', /** * Compute optimized instances, 5th generation @@ -91,7 +91,7 @@ export enum InstanceClass { /** * Storage-optimized instances, 2nd generation */ - Storage2 = 'd2', + STORAGE2 = 'd2', /** * Storage-optimized instances, 2nd generation @@ -101,7 +101,7 @@ export enum InstanceClass { /** * Storage/compute balanced instances, 1st generation */ - StorageCompute1 = 'h1', + STORAGE_COMPUTE_1 = 'h1', /** * Storage/compute balanced instances, 1st generation @@ -111,7 +111,7 @@ export enum InstanceClass { /** * I/O-optimized instances, 3rd generation */ - Io3 = 'i3', + IO3 = 'i3', /** * I/O-optimized instances, 3rd generation @@ -121,7 +121,7 @@ export enum InstanceClass { /** * Burstable instances, 2nd generation */ - Burstable2 = 't2', + BURSTABLE2 = 't2', /** * Burstable instances, 2nd generation @@ -131,7 +131,7 @@ export enum InstanceClass { /** * Burstable instances, 3rd generation */ - Burstable3 = 't3', + BURSTABLE3 = 't3', /** * Burstable instances, 3rd generation @@ -141,7 +141,7 @@ export enum InstanceClass { /** * Memory-intensive instances, 1st generation */ - MemoryIntensive1 = 'x1', + MEMORY_INTENSIVE_1 = 'x1', /** * Memory-intensive instances, 1st generation @@ -151,17 +151,17 @@ export enum InstanceClass { /** * Memory-intensive instances, extended, 1st generation */ - MemoryIntensive1Extended = 'x1e', + MEMORY_INTENSIVE_1_EXTENDED = 'x1e', /** * Memory-intensive instances, 1st generation */ - X1e = 'x1e', + X1E = 'x1e', /** * Instances with customizable hardware acceleration, 1st generation */ - Fpga1 = 'f1', + FPGA1 = 'f1', /** * Instances with customizable hardware acceleration, 1st generation @@ -171,7 +171,7 @@ export enum InstanceClass { /** * Graphics-optimized instances, 3rd generation */ - Graphics3 = 'g3', + GRAPHICS3 = 'g3', /** * Graphics-optimized instances, 3rd generation @@ -181,7 +181,7 @@ export enum InstanceClass { /** * Parallel-processing optimized instances, 2nd generation */ - Parallel2 = 'p2', + PARALLEL2 = 'p2', /** * Parallel-processing optimized instances, 2nd generation @@ -191,7 +191,7 @@ export enum InstanceClass { /** * Parallel-processing optimized instances, 3nd generation */ - Parallel3 = 'p3', + PARALLEL3 = 'p3', /** * Parallel-processing optimized instances, 3nd generation @@ -203,22 +203,22 @@ export enum InstanceClass { * What size of instance to use */ export enum InstanceSize { - Nano = 'nano', - Micro = 'micro', - Small = 'small', - Medium = 'medium', - Large = 'large', - XLarge = 'xlarge', - XLarge2 = '2xlarge', - XLarge4 = '4xlarge', - XLarge8 = '8xlarge', - XLarge9 = '9xlarge', - XLarge10 = '10xlarge', - XLarge12 = '12xlarge', - XLarge16 = '16xlarge', - XLarge18 = '18xlarge', - XLarge24 = '24xlarge', - XLarge32 = '32xlarge', + NANO = 'nano', + MICRO = 'micro', + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large', + XLARGE = 'xlarge', + XLARGE2 = '2xlarge', + XLARGE4 = '4xlarge', + XLARGE8 = '8xlarge', + XLARGE9 = '9xlarge', + XLARGE10 = '10xlarge', + XLARGE12 = '12xlarge', + XLARGE16 = '16xlarge', + XLARGE18 = '18xlarge', + XLARGE24 = '24xlarge', + XLARGE32 = '32xlarge', } /** @@ -228,6 +228,18 @@ export enum InstanceSize { * know the identifier of the type you want. */ export class InstanceType { + /** + * Instance type for EC2 instances + * + * This class takes a combination of a class and size. + * + * Be aware that not all combinations of class and size are available, and not all + * classes are available in all regions. + */ + public static of(instanceClass: InstanceClass, instanceSize: InstanceSize) { + return new InstanceType(`${instanceClass}.${instanceSize}`); + } + constructor(private readonly instanceTypeIdentifier: string) { } @@ -237,19 +249,4 @@ export class InstanceType { public toString(): string { return this.instanceTypeIdentifier; } -} - -/** - * Instance type for EC2 instances - * - * This class takes a combination of a class and size. - * - * Be aware that not all combinations of class and size are available, and not all - * classes are available in all regions. - */ -export class InstanceTypePair extends InstanceType { - constructor(public readonly instanceClass: InstanceClass, - public readonly instanceSize: InstanceSize) { - super(instanceClass + '.' + instanceSize); - } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index de885d2197e7a..f74ebbf37091f 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -1,14 +1,50 @@ import ssm = require('@aws-cdk/aws-ssm'); import { Construct, Stack, Token } from '@aws-cdk/cdk'; +import { UserData } from './user-data'; +import { WindowsVersion } from './windows-versions'; /** * Interface for classes that can select an appropriate machine image to use */ -export interface IMachineImageSource { +export interface IMachineImage { /** * Return the image to use in the given context */ - getImage(scope: Construct): MachineImage; + getImage(scope: Construct): MachineImageConfig; +} + +/** + * Configuration for a machine image + */ +export interface MachineImageConfig { + /** + * The AMI ID of the image to use + */ + readonly imageId: string; + + /** + * Operating system type for this image + */ + readonly osType: OperatingSystemType; + + /** + * Initial UserData for this image + * + * @default - Default UserData appropriate for the osType is created + */ + readonly userData?: UserData; +} + +/** + * Configuration options for WindowsImage + */ +export interface WindowsImageProps { + /** + * Initial user data + * + * @default - Empty UserData for Windows machines + */ + readonly userData?: UserData; } /** @@ -18,24 +54,28 @@ export interface IMachineImageSource { * * https://aws.amazon.com/blogs/mt/query-for-the-latest-windows-ami-using-systems-manager-parameter-store/ */ -export class WindowsImage implements IMachineImageSource { - constructor(private readonly version: WindowsVersion) { +export class WindowsImage implements IMachineImage { + constructor(private readonly version: WindowsVersion, private readonly props: WindowsImageProps = {}) { } /** * Return the image to use in the given context */ - public getImage(scope: Construct): MachineImage { - const parameterName = this.imageParameterName(this.version); + public getImage(scope: Construct): MachineImageConfig { + const parameterName = this.imageParameterName(); const ami = ssm.StringParameter.valueForStringParameter(scope, parameterName); - return new MachineImage(ami, new WindowsOS()); + return { + imageId: ami, + userData: this.props.userData, + osType: OperatingSystemType.WINDOWS, + }; } /** * Construct the SSM parameter name for the given Windows image */ - private imageParameterName(version: WindowsVersion): string { - return '/aws/service/ami-windows-latest/' + version; + private imageParameterName(): string { + return '/aws/service/ami-windows-latest/' + this.version; } } @@ -70,6 +110,13 @@ export interface AmazonLinuxImageProps { * @default GeneralPurpose */ readonly storage?: AmazonLinuxStorage; + + /** + * Initial user data + * + * @default - Empty UserData for Linux machines + */ + readonly userData?: UserData; } /** @@ -77,27 +124,27 @@ export interface AmazonLinuxImageProps { * * The AMI ID is selected using the values published to the SSM parameter store. */ -export class AmazonLinuxImage implements IMachineImageSource { +export class AmazonLinuxImage implements IMachineImage { private readonly generation: AmazonLinuxGeneration; private readonly edition: AmazonLinuxEdition; private readonly virtualization: AmazonLinuxVirt; private readonly storage: AmazonLinuxStorage; - constructor(props?: AmazonLinuxImageProps) { - this.generation = (props && props.generation) || AmazonLinuxGeneration.AmazonLinux; - this.edition = (props && props.edition) || AmazonLinuxEdition.Standard; + constructor(private readonly props: AmazonLinuxImageProps = {}) { + this.generation = (props && props.generation) || AmazonLinuxGeneration.AMAZON_LINUX; + this.edition = (props && props.edition) || AmazonLinuxEdition.STANDARD; this.virtualization = (props && props.virtualization) || AmazonLinuxVirt.HVM; - this.storage = (props && props.storage) || AmazonLinuxStorage.GeneralPurpose; + this.storage = (props && props.storage) || AmazonLinuxStorage.GENERAL_PURPOSE; } /** * Return the image to use in the given context */ - public getImage(scope: Construct): MachineImage { + public getImage(scope: Construct): MachineImageConfig { const parts: Array = [ this.generation, 'ami', - this.edition !== AmazonLinuxEdition.Standard ? this.edition : undefined, + this.edition !== AmazonLinuxEdition.STANDARD ? this.edition : undefined, this.virtualization, 'x86_64', // No 32-bits images vended through this this.storage @@ -105,7 +152,12 @@ export class AmazonLinuxImage implements IMachineImageSource { const parameterName = '/aws/service/ami-amazon-linux-latest/' + parts.join('-'); const ami = ssm.StringParameter.valueForStringParameter(scope, parameterName); - return new MachineImage(ami, new LinuxOS()); + + return { + imageId: ami, + userData: this.props.userData, + osType: OperatingSystemType.LINUX, + }; } } @@ -116,12 +168,12 @@ export enum AmazonLinuxGeneration { /** * Amazon Linux */ - AmazonLinux = 'amzn', + AMAZON_LINUX = 'amzn', /** * Amazon Linux 2 */ - AmazonLinux2 = 'amzn2', + AMAZON_LINUX_2 = 'amzn2', } /** @@ -131,12 +183,12 @@ export enum AmazonLinuxEdition { /** * Standard edition */ - Standard = 'standard', + STANDARD = 'standard', /** * Minimal edition */ - Minimal = 'minimal' + MINIMAL = 'minimal' } /** @@ -168,7 +220,19 @@ export enum AmazonLinuxStorage { /** * General Purpose-based storage (recommended) */ - GeneralPurpose = 'gp2', + GENERAL_PURPOSE = 'gp2', +} + +/** + * Configuration options for GenericLinuxImage + */ +export interface GenericLinuxImageProps { + /** + * Initial user data + * + * @default - Empty UserData for Windows machines + */ + readonly userData?: UserData; } /** @@ -177,11 +241,11 @@ export enum AmazonLinuxStorage { * Linux images IDs are not published to SSM parameter store yet, so you'll have to * manually specify an AMI map. */ -export class GenericLinuxImage implements IMachineImageSource { - constructor(private readonly amiMap: {[region: string]: string}) { +export class GenericLinuxImage implements IMachineImage { + constructor(private readonly amiMap: {[region: string]: string}, private readonly props: GenericLinuxImageProps = {}) { } - public getImage(scope: Construct): MachineImage { + public getImage(scope: Construct): MachineImageConfig { const region = Stack.of(scope).region; if (Token.isUnresolved(region)) { throw new Error(`Unable to determine AMI from AMI map since stack is region-agnostic`); @@ -192,249 +256,11 @@ export class GenericLinuxImage implements IMachineImageSource { throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); } - return new MachineImage(ami, new LinuxOS()); - } -} - -/** - * The Windows version to use for the WindowsImage - */ -export enum WindowsVersion { - WindowsServer2008SP2English64BitSQL2008SP4Express = 'Windows_Server-2008-SP2-English-64Bit-SQL_2008_SP4_Express', - WindowsServer2012R2RTMChineseSimplified64BitBase = 'Windows_Server-2012-R2_RTM-Chinese_Simplified-64Bit-Base', - WindowsServer2012R2RTMChineseTraditional64BitBase = 'Windows_Server-2012-R2_RTM-Chinese_Traditional-64Bit-Base', - WindowsServer2012R2RTMDutch64BitBase = 'Windows_Server-2012-R2_RTM-Dutch-64Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2014SP2Enterprise = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Enterprise', - WindowsServer2012R2RTMHungarian64BitBase = 'Windows_Server-2012-R2_RTM-Hungarian-64Bit-Base', - WindowsServer2012R2RTMJapanese64BitBase = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-Base', - WindowsServer2016EnglishCoreContainers = 'Windows_Server-2016-English-Core-Containers', - WindowsServer2016EnglishCoreSQL2016SP1Web = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Web', - WindowsServer2016GermanFullBase = 'Windows_Server-2016-German-Full-Base', - WindowsServer2003R2SP2LanguagePacks32BitBase = 'Windows_Server-2003-R2_SP2-Language_Packs-32Bit-Base', - WindowsServer2008R2SP1English64BitSQL2008R2SP3Web = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Web', - WindowsServer2008R2SP1English64BitSQL2012SP4Express = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Express', - WindowsServer2008R2SP1PortugueseBrazil64BitCore = 'Windows_Server-2008-R2_SP1-Portuguese_Brazil-64Bit-Core', - WindowsServer2012R2RTMEnglish64BitSQL2016SP2Standard = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Standard', - WindowsServer2012RTMEnglish64BitSQL2014SP2Express = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Express', - WindowsServer2012RTMItalian64BitBase = 'Windows_Server-2012-RTM-Italian-64Bit-Base', - WindowsServer2016EnglishCoreSQL2016SP1Express = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Express', - WindowsServer2016EnglishDeepLearning = 'Windows_Server-2016-English-Deep-Learning', - WindowsServer2019ItalianFullBase = 'Windows_Server-2019-Italian-Full-Base', - WindowsServer2008R2SP1Korean64BitBase = 'Windows_Server-2008-R2_SP1-Korean-64Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2016SP1Express = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Express', - WindowsServer2012R2RTMJapanese64BitSQL2016SP2Web = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Web', - WindowsServer2016JapaneseFullSQL2016SP2Web = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Web', - WindowsServer2016KoreanFullBase = 'Windows_Server-2016-Korean-Full-Base', - WindowsServer2016KoreanFullSQL2016SP2Standard = 'Windows_Server-2016-Korean-Full-SQL_2016_SP2_Standard', - WindowsServer2016PortuguesePortugalFullBase = 'Windows_Server-2016-Portuguese_Portugal-Full-Base', - WindowsServer2019EnglishFullSQL2017Web = 'Windows_Server-2019-English-Full-SQL_2017_Web', - WindowsServer2019FrenchFullBase = 'Windows_Server-2019-French-Full-Base', - WindowsServer2019KoreanFullBase = 'Windows_Server-2019-Korean-Full-Base', - WindowsServer2008R2SP1ChineseHongKongSAR64BitBase = 'Windows_Server-2008-R2_SP1-Chinese_Hong_Kong_SAR-64Bit-Base', - WindowsServer2008R2SP1ChinesePRC64BitBase = 'Windows_Server-2008-R2_SP1-Chinese_PRC-64Bit-Base', - WindowsServer2012RTMFrench64BitBase = 'Windows_Server-2012-RTM-French-64Bit-Base', - WindowsServer2016EnglishFullContainers = 'Windows_Server-2016-English-Full-Containers', - WindowsServer2016EnglishFullSQL2016SP1Standard = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Standard', - WindowsServer2016RussianFullBase = 'Windows_Server-2016-Russian-Full-Base', - WindowsServer2019ChineseSimplifiedFullBase = 'Windows_Server-2019-Chinese_Simplified-Full-Base', - WindowsServer2019EnglishFullSQL2016SP2Standard = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Standard', - WindowsServer2019HungarianFullBase = 'Windows_Server-2019-Hungarian-Full-Base', - WindowsServer2008R2SP1English64BitSQL2008R2SP3Express = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Express', - WindowsServer2008R2SP1LanguagePacks64BitBase = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-Base', - WindowsServer2008SP2English32BitBase = 'Windows_Server-2008-SP2-English-32Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2012SP4Enterprise = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2012_SP4_Enterprise', - WindowsServer2012RTMChineseTraditional64BitBase = 'Windows_Server-2012-RTM-Chinese_Traditional-64Bit-Base', - WindowsServer2012RTMEnglish64BitSQL2008R2SP3Express = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Express', - WindowsServer2012RTMEnglish64BitSQL2014SP2Standard = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Standard', - WindowsServer2012RTMJapanese64BitSQL2014SP2Express = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Express', - WindowsServer2016PolishFullBase = 'Windows_Server-2016-Polish-Full-Base', - WindowsServer2019EnglishFullSQL2016SP2Web = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Web', - WindowsServer2012R2RTMEnglish64BitSQL2014SP3Standard = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Standard', - WindowsServer2012R2RTMEnglish64BitSQL2016SP2Express = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Express', - WindowsServer2012R2RTMEnglishDeepLearning = 'Windows_Server-2012-R2_RTM-English-Deep-Learning', - WindowsServer2012R2RTMGerman64BitBase = 'Windows_Server-2012-R2_RTM-German-64Bit-Base', - WindowsServer2012R2RTMJapanese64BitSQL2016SP1Express = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Express', - WindowsServer2012R2RTMRussian64BitBase = 'Windows_Server-2012-R2_RTM-Russian-64Bit-Base', - WindowsServer2012RTMChineseTraditionalHongKongSAR64BitBase = 'Windows_Server-2012-RTM-Chinese_Traditional_Hong_Kong_SAR-64Bit-Base', - WindowsServer2012RTMHungarian64BitBase = 'Windows_Server-2012-RTM-Hungarian-64Bit-Base', - WindowsServer2012RTMJapanese64BitSQL2014SP3Standard = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Standard', - WindowsServer2019EnglishFullHyperV = 'Windows_Server-2019-English-Full-HyperV', - WindowsServer2003R2SP2English64BitSQL2005SP4Express = 'Windows_Server-2003-R2_SP2-English-64Bit-SQL_2005_SP4_Express', - WindowsServer2008R2SP1Japanese64BitSQL2012SP4Express = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2012_SP4_Express', - WindowsServer2012RTMGerman64BitBase = 'Windows_Server-2012-RTM-German-64Bit-Base', - WindowsServer2012RTMJapanese64BitSQL2008R2SP3Standard = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2008_R2_SP3_Standard', - WindowsServer2016EnglishFullSQL2016SP2Standard = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Standard', - WindowsServer2019EnglishFullSQL2017Express = 'Windows_Server-2019-English-Full-SQL_2017_Express', - WindowsServer2019JapaneseFullBase = 'Windows_Server-2019-Japanese-Full-Base', - WindowsServer2019RussianFullBase = 'Windows_Server-2019-Russian-Full-Base', - WindowsServer2012R2RTMEnglish64BitSQL2014SP2Standard = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Standard', - WindowsServer2012R2RTMItalian64BitBase = 'Windows_Server-2012-R2_RTM-Italian-64Bit-Base', - WindowsServer2012RTMEnglish64BitBase = 'Windows_Server-2012-RTM-English-64Bit-Base', - WindowsServer2012RTMEnglish64BitSQL2008R2SP3Standard = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Standard', - WindowsServer2016EnglishFullHyperV = 'Windows_Server-2016-English-Full-HyperV', - WindowsServer2016EnglishFullSQL2016SP2Enterprise = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Enterprise', - WindowsServer2019ChineseTraditionalFullBase = 'Windows_Server-2019-Chinese_Traditional-Full-Base', - WindowsServer2019EnglishCoreBase = 'Windows_Server-2019-English-Core-Base', - WindowsServer2019EnglishCoreContainersLatest = 'Windows_Server-2019-English-Core-ContainersLatest', - WindowsServer2008SP2English64BitBase = 'Windows_Server-2008-SP2-English-64Bit-Base', - WindowsServer2012R2RTMFrench64BitBase = 'Windows_Server-2012-R2_RTM-French-64Bit-Base', - WindowsServer2012R2RTMPolish64BitBase = 'Windows_Server-2012-R2_RTM-Polish-64Bit-Base', - WindowsServer2012RTMEnglish64BitSQL2012SP4Express = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Express', - WindowsServer2012RTMEnglish64BitSQL2014SP3Standard = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Standard', - WindowsServer2012RTMJapanese64BitSQL2012SP4Standard = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Standard', - WindowsServer2016EnglishCoreContainersLatest = 'Windows_Server-2016-English-Core-ContainersLatest', - WindowsServer2019EnglishFullSQL2016SP2Express = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Express', - WindowsServer2019TurkishFullBase = 'Windows_Server-2019-Turkish-Full-Base', - WindowsServer2012R2RTMEnglish64BitSQL2014SP2Express = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Express', - WindowsServer2012R2RTMEnglish64BitSQL2014SP3Web = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Web', - WindowsServer2012R2RTMJapanese64BitSQL2016SP1Web = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Web', - WindowsServer2012R2RTMPortugueseBrazil64BitBase = 'Windows_Server-2012-R2_RTM-Portuguese_Brazil-64Bit-Base', - WindowsServer2012R2RTMPortuguesePortugal64BitBase = 'Windows_Server-2012-R2_RTM-Portuguese_Portugal-64Bit-Base', - WindowsServer2012R2RTMSwedish64BitBase = 'Windows_Server-2012-R2_RTM-Swedish-64Bit-Base', - WindowsServer2016EnglishFullSQL2016SP1Express = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Express', - WindowsServer2016ItalianFullBase = 'Windows_Server-2016-Italian-Full-Base', - WindowsServer2016SpanishFullBase = 'Windows_Server-2016-Spanish-Full-Base', - WindowsServer2019EnglishFullSQL2017Standard = 'Windows_Server-2019-English-Full-SQL_2017_Standard', - WindowsServer2003R2SP2LanguagePacks64BitSQL2005SP4Standard = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-SQL_2005_SP4_Standard', - WindowsServer2008R2SP1Japanese64BitSQL2008R2SP3Standard = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Standard', - WindowsServer2012R2RTMJapanese64BitSQL2016SP1Standard = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Standard', - WindowsServer2012RTMEnglish64BitSQL2008R2SP3Web = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Web', - WindowsServer2012RTMJapanese64BitSQL2014SP2Web = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Web', - WindowsServer2016EnglishCoreSQL2016SP2Enterprise = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Enterprise', - WindowsServer2016PortugueseBrazilFullBase = 'Windows_Server-2016-Portuguese_Brazil-Full-Base', - WindowsServer2019EnglishFullBase = 'Windows_Server-2019-English-Full-Base', - WindowsServer2003R2SP2English32BitBase = 'Windows_Server-2003-R2_SP2-English-32Bit-Base', - WindowsServer2012R2RTMCzech64BitBase = 'Windows_Server-2012-R2_RTM-Czech-64Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2016SP1Standard = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Standard', - WindowsServer2012R2RTMJapanese64BitSQL2014SP2Express = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP2_Express', - WindowsServer2012RTMEnglish64BitSQL2012SP4Standard = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Standard', - WindowsServer2016EnglishCoreSQL2016SP1Enterprise = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Enterprise', - WindowsServer2016JapaneseFullSQL2016SP1Web = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Web', - WindowsServer2016SwedishFullBase = 'Windows_Server-2016-Swedish-Full-Base', - WindowsServer2016TurkishFullBase = 'Windows_Server-2016-Turkish-Full-Base', - WindowsServer2008R2SP1English64BitCoreSQL2012SP4Standard = 'Windows_Server-2008-R2_SP1-English-64Bit-Core_SQL_2012_SP4_Standard', - WindowsServer2008R2SP1LanguagePacks64BitSQL2008R2SP3Standard = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-SQL_2008_R2_SP3_Standard', - WindowsServer2012RTMCzech64BitBase = 'Windows_Server-2012-RTM-Czech-64Bit-Base', - WindowsServer2012RTMTurkish64BitBase = 'Windows_Server-2012-RTM-Turkish-64Bit-Base', - WindowsServer2016DutchFullBase = 'Windows_Server-2016-Dutch-Full-Base', - WindowsServer2016EnglishFullSQL2016SP2Express = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Express', - WindowsServer2016EnglishFullSQL2017Enterprise = 'Windows_Server-2016-English-Full-SQL_2017_Enterprise', - WindowsServer2016HungarianFullBase = 'Windows_Server-2016-Hungarian-Full-Base', - WindowsServer2016KoreanFullSQL2016SP1Standard = 'Windows_Server-2016-Korean-Full-SQL_2016_SP1_Standard', - WindowsServer2019SpanishFullBase = 'Windows_Server-2019-Spanish-Full-Base', - WindowsServer2003R2SP2English64BitBase = 'Windows_Server-2003-R2_SP2-English-64Bit-Base', - WindowsServer2008R2SP1English64BitBase = 'Windows_Server-2008-R2_SP1-English-64Bit-Base', - WindowsServer2008R2SP1LanguagePacks64BitSQL2008R2SP3Express = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-SQL_2008_R2_SP3_Express', - WindowsServer2008SP2PortugueseBrazil64BitBase = 'Windows_Server-2008-SP2-Portuguese_Brazil-64Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2016SP1Web = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Web', - WindowsServer2012R2RTMJapanese64BitSQL2014SP3Express = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP3_Express', - WindowsServer2012R2RTMJapanese64BitSQL2016SP2Enterprise = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Enterprise', - WindowsServer2012RTMJapanese64BitBase = 'Windows_Server-2012-RTM-Japanese-64Bit-Base', - WindowsServer2019EnglishFullContainersLatest = 'Windows_Server-2019-English-Full-ContainersLatest', - WindowsServer2019EnglishFullSQL2017Enterprise = 'Windows_Server-2019-English-Full-SQL_2017_Enterprise', - WindowsServer1709EnglishCoreContainersLatest = 'Windows_Server-1709-English-Core-ContainersLatest', - WindowsServer1803EnglishCoreBase = 'Windows_Server-1803-English-Core-Base', - WindowsServer2008R2SP1English64BitSQL2012SP4Web = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Web', - WindowsServer2008R2SP1Japanese64BitBase = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-Base', - WindowsServer2008SP2English64BitSQL2008SP4Standard = 'Windows_Server-2008-SP2-English-64Bit-SQL_2008_SP4_Standard', - WindowsServer2012R2RTMEnglish64BitBase = 'Windows_Server-2012-R2_RTM-English-64Bit-Base', - WindowsServer2012RTMPortugueseBrazil64BitBase = 'Windows_Server-2012-RTM-Portuguese_Brazil-64Bit-Base', - WindowsServer2016EnglishFullSQL2016SP1Web = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Web', - WindowsServer2016EnglishP3 = 'Windows_Server-2016-English-P3', - WindowsServer2016JapaneseFullSQL2016SP1Enterprise = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Enterprise', - WindowsServer2003R2SP2LanguagePacks64BitBase = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-Base', - WindowsServer2012R2RTMChineseTraditionalHongKong64BitBase = 'Windows_Server-2012-R2_RTM-Chinese_Traditional_Hong_Kong-64Bit-Base', - WindowsServer2012R2RTMEnglish64BitSQL2014SP3Express = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Express', - WindowsServer2012R2RTMEnglish64BitSQL2016SP2Enterprise = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Enterprise', - WindowsServer2012RTMChineseSimplified64BitBase = 'Windows_Server-2012-RTM-Chinese_Simplified-64Bit-Base', - WindowsServer2012RTMEnglish64BitSQL2012SP4Web = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Web', - WindowsServer2012RTMJapanese64BitSQL2014SP3Web = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Web', - WindowsServer2016JapaneseFullBase = 'Windows_Server-2016-Japanese-Full-Base', - WindowsServer2016JapaneseFullSQL2016SP1Express = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Express', - WindowsServer1803EnglishCoreContainersLatest = 'Windows_Server-1803-English-Core-ContainersLatest', - WindowsServer2008R2SP1Japanese64BitSQL2012SP4Standard = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2012_SP4_Standard', - WindowsServer2012R2RTMEnglish64BitCore = 'Windows_Server-2012-R2_RTM-English-64Bit-Core', - WindowsServer2012R2RTMEnglish64BitSQL2014SP2Web = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Web', - WindowsServer2012R2RTMEnglish64BitSQL2014SP3Enterprise = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Enterprise', - WindowsServer2012R2RTMJapanese64BitSQL2016SP2Standard = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Standard', - WindowsServer2012RTMEnglish64BitSQL2014SP3Web = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Web', - WindowsServer2012RTMSwedish64BitBase = 'Windows_Server-2012-RTM-Swedish-64Bit-Base', - WindowsServer2016ChineseSimplifiedFullBase = 'Windows_Server-2016-Chinese_Simplified-Full-Base', - WindowsServer2019PolishFullBase = 'Windows_Server-2019-Polish-Full-Base', - WindowsServer2008R2SP1Japanese64BitSQL2008R2SP3Web = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Web', - WindowsServer2008R2SP1PortugueseBrazil64BitBase = 'Windows_Server-2008-R2_SP1-Portuguese_Brazil-64Bit-Base', - WindowsServer2012R2RTMJapanese64BitSQL2016SP1Enterprise = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Enterprise', - WindowsServer2012R2RTMJapanese64BitSQL2016SP2Express = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Express', - WindowsServer2012RTMEnglish64BitSQL2014SP3Express = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Express', - WindowsServer2012RTMJapanese64BitSQL2014SP2Standard = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Standard', - WindowsServer2016EnglishCoreBase = 'Windows_Server-2016-English-Core-Base', - WindowsServer2016EnglishFullBase = 'Windows_Server-2016-English-Full-Base', - WindowsServer2016EnglishFullSQL2017Web = 'Windows_Server-2016-English-Full-SQL_2017_Web', - WindowsServer2019GermanFullBase = 'Windows_Server-2019-German-Full-Base', - WindowsServer2003R2SP2English64BitSQL2005SP4Standard = 'Windows_Server-2003-R2_SP2-English-64Bit-SQL_2005_SP4_Standard', - WindowsServer2008R2SP1English64BitSQL2012SP4Enterprise = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Enterprise', - WindowsServer2008R2SP1Japanese64BitSQL2008R2SP3Express = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Express', - WindowsServer2012R2RTMEnglish64BitSQL2016SP1Enterprise = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Enterprise', - WindowsServer2012RTMEnglish64BitSQL2014SP2Web = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Web', - WindowsServer2012RTMJapanese64BitSQL2008R2SP3Express = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2008_R2_SP3_Express', - WindowsServer2016FrenchFullBase = 'Windows_Server-2016-French-Full-Base', - WindowsServer2016JapaneseFullSQL2016SP2Enterprise = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Enterprise', - WindowsServer2019CzechFullBase = 'Windows_Server-2019-Czech-Full-Base', - WindowsServer1809EnglishCoreBase = 'Windows_Server-1809-English-Core-Base', - WindowsServer1809EnglishCoreContainersLatest = 'Windows_Server-1809-English-Core-ContainersLatest', - WindowsServer2003R2SP2LanguagePacks64BitSQL2005SP4Express = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-SQL_2005_SP4_Express', - WindowsServer2012R2RTMTurkish64BitBase = 'Windows_Server-2012-R2_RTM-Turkish-64Bit-Base', - WindowsServer2012RTMJapanese64BitSQL2012SP4Web = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Web', - WindowsServer2012RTMPolish64BitBase = 'Windows_Server-2012-RTM-Polish-64Bit-Base', - WindowsServer2012RTMSpanish64BitBase = 'Windows_Server-2012-RTM-Spanish-64Bit-Base', - WindowsServer2016EnglishFullSQL2016SP1Enterprise = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Enterprise', - WindowsServer2016JapaneseFullSQL2016SP2Express = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Express', - WindowsServer2019EnglishFullSQL2016SP2Enterprise = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Enterprise', - WindowsServer1709EnglishCoreBase = 'Windows_Server-1709-English-Core-Base', - WindowsServer2008R2SP1English64BitSQL2012RTMSP2Enterprise = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_RTM_SP2_Enterprise', - WindowsServer2008R2SP1English64BitSQL2012SP4Standard = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Standard', - WindowsServer2008SP2PortugueseBrazil32BitBase = 'Windows_Server-2008-SP2-Portuguese_Brazil-32Bit-Base', - WindowsServer2012R2RTMJapanese64BitSQL2014SP2Standard = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP2_Standard', - WindowsServer2012RTMJapanese64BitSQL2012SP4Express = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Express', - WindowsServer2012RTMPortuguesePortugal64BitBase = 'Windows_Server-2012-RTM-Portuguese_Portugal-64Bit-Base', - WindowsServer2016CzechFullBase = 'Windows_Server-2016-Czech-Full-Base', - WindowsServer2016JapaneseFullSQL2016SP1Standard = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Standard', - WindowsServer2019DutchFullBase = 'Windows_Server-2019-Dutch-Full-Base', - WindowsServer2008R2SP1English64BitCore = 'Windows_Server-2008-R2_SP1-English-64Bit-Core', - WindowsServer2012R2RTMEnglish64BitSQL2016SP2Web = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Web', - WindowsServer2012R2RTMKorean64BitBase = 'Windows_Server-2012-R2_RTM-Korean-64Bit-Base', - WindowsServer2012RTMDutch64BitBase = 'Windows_Server-2012-RTM-Dutch-64Bit-Base', - WindowsServer2016English64BitSQL2012SP4Enterprise = 'Windows_Server-2016-English-64Bit-SQL_2012_SP4_Enterprise', - WindowsServer2016EnglishCoreSQL2016SP1Standard = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Standard', - WindowsServer2016EnglishCoreSQL2016SP2Express = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Express', - WindowsServer2016EnglishCoreSQL2016SP2Web = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Web', - WindowsServer2016EnglishFullSQL2017Standard = 'Windows_Server-2016-English-Full-SQL_2017_Standard', - WindowsServer2019PortugueseBrazilFullBase = 'Windows_Server-2019-Portuguese_Brazil-Full-Base', - WindowsServer2008R2SP1English64BitSQL2008R2SP3Standard = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Standard', - WindowsServer2008R2SP1English64BitSharePoint2010SP2Foundation = 'Windows_Server-2008-R2_SP1-English-64Bit-SharePoint_2010_SP2_Foundation', - WindowsServer2012R2RTMEnglishP3 = 'Windows_Server-2012-R2_RTM-English-P3', - WindowsServer2012R2RTMJapanese64BitSQL2014SP3Standard = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP3_Standard', - WindowsServer2012R2RTMSpanish64BitBase = 'Windows_Server-2012-R2_RTM-Spanish-64Bit-Base', - WindowsServer2012RTMJapanese64BitSQL2014SP3Express = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Express', - WindowsServer2016EnglishCoreSQL2016SP2Standard = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Standard', - WindowsServer2016JapaneseFullSQL2016SP2Standard = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Standard', - WindowsServer2019PortuguesePortugalFullBase = 'Windows_Server-2019-Portuguese_Portugal-Full-Base', - WindowsServer2019SwedishFullBase = 'Windows_Server-2019-Swedish-Full-Base', - WindowsServer2012R2RTMEnglish64BitHyperV = 'Windows_Server-2012-R2_RTM-English-64Bit-HyperV', - WindowsServer2012RTMKorean64BitBase = 'Windows_Server-2012-RTM-Korean-64Bit-Base', - WindowsServer2012RTMRussian64BitBase = 'Windows_Server-2012-RTM-Russian-64Bit-Base', - WindowsServer2016ChineseTraditionalFullBase = 'Windows_Server-2016-Chinese_Traditional-Full-Base', - WindowsServer2016EnglishFullSQL2016SP2Web = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Web', - WindowsServer2016EnglishFullSQL2017Express = 'Windows_Server-2016-English-Full-SQL_2017_Express', -} - -/** - * Representation of a machine to be launched - * - * Combines an AMI ID with an OS. - */ -export class MachineImage { - constructor(public readonly imageId: string, public readonly os: OperatingSystem) { + return { + imageId: ami, + userData: this.props.userData, + osType: OperatingSystemType.LINUX, + }; } } @@ -442,40 +268,6 @@ export class MachineImage { * The OS type of a particular image */ export enum OperatingSystemType { - Linux, - Windows, -} - -/** - * Abstraction of OS features we need to be aware of - */ -export abstract class OperatingSystem { - public abstract createUserData(scripts: string[]): string; - abstract get type(): OperatingSystemType; -} - -/** - * OS features specialized for Windows - */ -export class WindowsOS extends OperatingSystem { - public createUserData(scripts: string[]): string { - return `${scripts.join('\n')}`; - } - - get type(): OperatingSystemType { - return OperatingSystemType.Windows; - } -} - -/** - * OS features specialized for Linux - */ -export class LinuxOS extends OperatingSystem { - public createUserData(scripts: string[]): string { - return '#!/bin/bash\n' + scripts.join('\n'); - } - - get type(): OperatingSystemType { - return OperatingSystemType.Linux; - } -} + LINUX, + WINDOWS, +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/peer.ts b/packages/@aws-cdk/aws-ec2/lib/peer.ts new file mode 100644 index 0000000000000..2e12fd58dd4d9 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/peer.ts @@ -0,0 +1,166 @@ +import { Connections, IConnectable } from "./connections"; + +/** + * Interface for classes that provide the peer-specification parts of a security group rule + */ +export interface IPeer extends IConnectable { + /** + * Whether the rule can be inlined into a SecurityGroup or not + */ + readonly canInlineRule: boolean; + + /** + * A unique identifier for this connection peer + */ + readonly uniqueId: string; + + /** + * Produce the ingress rule JSON for the given connection + */ + toIngressRuleConfig(): any; + + /** + * Produce the egress rule JSON for the given connection + */ + toEgressRuleConfig(): any; +} + +/** + * Factories for static connection peer + */ +export class Peer { + /** + * Create an IPv4 peer from a CIDR + */ + public static ipv4(cidrIp: string): IPeer { + return new CidrIPv4(cidrIp); + } + + /** + * Any IPv4 address + */ + public static anyIpv4(): IPeer { + return new AnyIPv4(); + } + + /** + * Create an IPv6 peer from a CIDR + */ + public static ipv6(cidrIp: string): IPeer { + return new CidrIPv6(cidrIp); + } + + /** + * Any IPv6 address + */ + public static anyIpv6(): IPeer { + return new AnyIPv6(); + } + + /** + * A prefix list + */ + public static prefixList(prefixListId: string): IPeer { + return new PrefixList(prefixListId); + } + + protected constructor() { + } +} + +/** + * A connection to and from a given IP range + */ +class CidrIPv4 implements IPeer { + public readonly canInlineRule = true; + public readonly connections: Connections = new Connections({ peer: this }); + public readonly uniqueId: string; + + constructor(private readonly cidrIp: string) { + this.uniqueId = cidrIp; + } + + /** + * Produce the ingress rule JSON for the given connection + */ + public toIngressRuleConfig(): any { + return { cidrIp: this.cidrIp }; + } + /** + * Produce the egress rule JSON for the given connection + */ + public toEgressRuleConfig(): any { + return { cidrIp: this.cidrIp }; + } +} + +/** + * Any IPv4 address + */ +class AnyIPv4 extends CidrIPv4 { + constructor() { + super("0.0.0.0/0"); + } +} + +/** + * A connection to a from a given IPv6 range + */ +class CidrIPv6 implements IPeer { + public readonly canInlineRule = true; + public readonly connections: Connections = new Connections({ peer: this }); + public readonly uniqueId: string; + + constructor(private readonly cidrIpv6: string) { + this.uniqueId = cidrIpv6; + } + + /** + * Produce the ingress rule JSON for the given connection + */ + public toIngressRuleConfig(): any { + return { cidrIpv6: this.cidrIpv6 }; + } + /** + * Produce the egress rule JSON for the given connection + */ + public toEgressRuleConfig(): any { + return { cidrIpv6: this.cidrIpv6 }; + } +} + +/** + * Any IPv6 address + */ +class AnyIPv6 extends CidrIPv6 { + constructor() { + super("::0/0"); + } +} + +/** + * A prefix list + * + * Prefix lists are used to allow traffic to VPC-local service endpoints. + * + * For more information, see this page: + * + * https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-endpoints.html + */ +class PrefixList implements IPeer { + public readonly canInlineRule = false; + public readonly connections: Connections = new Connections({ peer: this }); + public readonly uniqueId: string; + + constructor(private readonly prefixListId: string) { + this.uniqueId = prefixListId; + } + + public toIngressRuleConfig(): any { + return { sourcePrefixListId: this.prefixListId }; + } + + public toEgressRuleConfig(): any { + return { destinationPrefixListId: this.prefixListId }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/port.ts b/packages/@aws-cdk/aws-ec2/lib/port.ts new file mode 100644 index 0000000000000..cd8985c4660b0 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/port.ts @@ -0,0 +1,201 @@ +import { Token } from '@aws-cdk/cdk'; + +/** + * Protocol for use in Connection Rules + */ +export enum Protocol { + ALL = '-1', + TCP = 'tcp', + UDP = 'udp', + ICMP = 'icmp', + ICMPV6 = '58', +} + +/** + * Properties to create a port range + */ +export interface PortProps { + /** + * The protocol for the range + */ + readonly protocol: Protocol; + + /** + * The starting port for the range + * + * @default - Not included in the rule + */ + readonly fromPort?: number; + + /** + * The ending port for the range + * + * @default - Not included in the rule + */ + readonly toPort?: number; + + /** + * String representation for this object + */ + readonly stringRepresentation: string; +} + +/** + * Interface for classes that provide the connection-specification parts of a security group rule + */ +export class Port { + /** + * A single TCP port + */ + public static tcp(port: number): Port { + return new Port({ + protocol: Protocol.TCP, + fromPort: port, + toPort: port, + stringRepresentation: renderPort(port), + }); + } + + /** + * A TCP port range + */ + public static tcpRange(startPort: number, endPort: number) { + return new Port({ + protocol: Protocol.TCP, + fromPort: startPort, + toPort: endPort, + stringRepresentation: `${renderPort(startPort)}-${renderPort(endPort)}` + }); + } + + /** + * Any TCP traffic + */ + public static allTcp() { + return new Port({ + protocol: Protocol.TCP, + fromPort: 0, + toPort: 65535, + stringRepresentation: 'ALL PORTS', + }); + } + + /** + * A single UDP port + */ + public static udp(port: number): Port { + return new Port({ + protocol: Protocol.UDP, + fromPort: port, + toPort: port, + stringRepresentation: `UDP ${renderPort(port)}`, + }); + } + + /** + * A UDP port range + */ + public static udpRange(startPort: number, endPort: number) { + return new Port({ + protocol: Protocol.UDP, + fromPort: startPort, + toPort: endPort, + stringRepresentation: `UDP ${renderPort(startPort)}-${renderPort(endPort)}` + }); + } + + /** + * Any UDP traffic + */ + public static allUdp() { + return new Port({ + protocol: Protocol.UDP, + fromPort: 0, + toPort: 65535, + stringRepresentation: 'UDP ALL PORTS', + }); + } + + /** + * A specific combination of ICMP type and code + * + * @see https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml + */ + public static icmpTypeAndCode(type: number, code: number) { + return new Port({ + protocol: Protocol.ICMP, + fromPort: type, + toPort: code, + stringRepresentation: `ICMP Type ${type} Code ${code}` + }); + } + + /** + * All codes for a single ICMP type + */ + public static icmpType(type: number): Port { + return new Port({ + protocol: Protocol.ICMP, + fromPort: type, + toPort: -1, + stringRepresentation: `ICMP Type ${type}`, + }); + } + + /** + * ICMP ping (echo) traffic + */ + public static icmpPing() { + return Port.icmpType(8); + } + + /** + * All ICMP traffic + */ + public static allIcmp() { + return new Port({ + protocol: Protocol.ICMP, + fromPort: -1, + toPort: -1, + stringRepresentation: 'ALL ICMP', + }); + } + + /** + * All traffic + */ + public static allTraffic() { + return new Port({ + protocol: Protocol.ALL, + stringRepresentation: 'ALL TRAFFIC', + }); + } + + /** + * Whether the rule containing this port range can be inlined into a securitygroup or not. + */ + public readonly canInlineRule: boolean; + + constructor(private readonly props: PortProps) { + this.canInlineRule = !Token.isUnresolved(props.fromPort) && !Token.isUnresolved(props.toPort); + } + + /** + * Produce the ingress/egress rule JSON for the given connection + */ + public toRuleJSON(): any { + return { + ipProtocol: this.props.protocol, + fromPort: this.props.fromPort, + toPort: this.props.toPort, + }; + } + + public toString(): string { + return this.props.stringRepresentation; + } +} + +function renderPort(port: number) { + return Token.isUnresolved(port) ? `{IndirectPort}` : port.toString(); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts b/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts deleted file mode 100644 index e968eeb9555ff..0000000000000 --- a/packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { Token } from '@aws-cdk/cdk'; -import { Connections, IConnectable } from "./connections"; - -/** - * Interface for classes that provide the peer-specification parts of a security group rule - */ -export interface ISecurityGroupRule { - /** - * Whether the rule can be inlined into a SecurityGroup or not - */ - readonly canInlineRule: boolean; - - /** - * A unique identifier for this connection peer - */ - readonly uniqueId: string; - - /** - * Produce the ingress rule JSON for the given connection - */ - toIngressRuleJSON(): any; - - /** - * Produce the egress rule JSON for the given connection - */ - toEgressRuleJSON(): any; -} - -/** - * A connection to and from a given IP range - */ -export class CidrIPv4 implements ISecurityGroupRule, IConnectable { - public readonly canInlineRule = true; - public readonly connections: Connections = new Connections({ securityGroupRule: this }); - public readonly uniqueId: string; - - constructor(private readonly cidrIp: string) { - this.uniqueId = cidrIp; - } - - /** - * Produce the ingress rule JSON for the given connection - */ - public toIngressRuleJSON(): any { - return { cidrIp: this.cidrIp }; - } - /** - * Produce the egress rule JSON for the given connection - */ - public toEgressRuleJSON(): any { - return { cidrIp: this.cidrIp }; - } -} - -/** - * Any IPv4 address - */ -export class AnyIPv4 extends CidrIPv4 { - constructor() { - super("0.0.0.0/0"); - } -} - -/** - * A connection to a from a given IPv6 range - */ -export class CidrIPv6 implements ISecurityGroupRule, IConnectable { - public readonly canInlineRule = true; - public readonly connections: Connections = new Connections({ securityGroupRule: this }); - public readonly uniqueId: string; - - constructor(private readonly cidrIpv6: string) { - this.uniqueId = cidrIpv6; - } - - /** - * Produce the ingress rule JSON for the given connection - */ - public toIngressRuleJSON(): any { - return { cidrIpv6: this.cidrIpv6 }; - } - /** - * Produce the egress rule JSON for the given connection - */ - public toEgressRuleJSON(): any { - return { cidrIpv6: this.cidrIpv6 }; - } -} - -/** - * Any IPv6 address - */ -export class AnyIPv6 extends CidrIPv6 { - constructor() { - super("::0/0"); - } -} - -/** - * A prefix list - * - * Prefix lists are used to allow traffic to VPC-local service endpoints. - * - * For more information, see this page: - * - * https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-endpoints.html - */ -export class PrefixList implements ISecurityGroupRule, IConnectable { - public readonly canInlineRule = false; - public readonly connections: Connections = new Connections({ securityGroupRule: this }); - public readonly uniqueId: string; - - constructor(private readonly prefixListId: string) { - this.uniqueId = prefixListId; - } - - public toIngressRuleJSON(): any { - return { sourcePrefixListId: this.prefixListId }; - } - - public toEgressRuleJSON(): any { - return { destinationPrefixListId: this.prefixListId }; - } -} - -/** - * Interface for classes that provide the connection-specification parts of a security group rule - */ -export interface IPortRange { - /** - * Whether the rule containing this port range can be inlined into a securitygroup or not. - */ - readonly canInlineRule: boolean; - - /** - * Produce the ingress/egress rule JSON for the given connection - */ - toRuleJSON(): any; -} - -/** - * Protocol for use in Connection Rules - */ -export enum Protocol { - All = '-1', - Tcp = 'tcp', - Udp = 'udp', - Icmp = 'icmp', - Icmpv6 = '58', -} - -/** - * A single TCP port - */ -export class TcpPort implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.port); - - constructor(private readonly port: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Tcp, - fromPort: this.port, - toPort: this.port - }; - } - - public toString() { - return Token.isUnresolved(this.port) ? `{IndirectPort}` : this.port.toString(); - } -} - -/** - * A TCP port range - */ -export class TcpPortRange implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.startPort) && !Token.isUnresolved(this.endPort); - - constructor(private readonly startPort: number, private readonly endPort: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Tcp, - fromPort: this.startPort, - toPort: this.endPort - }; - } - - public toString() { - return `${this.startPort}-${this.endPort}`; - } -} - -/** - * All TCP Ports - */ -export class TcpAllPorts implements IPortRange { - public readonly canInlineRule = true; - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Tcp, - fromPort: 0, - toPort: 65535 - }; - } - - public toString() { - return 'ALL PORTS'; - } -} - -/** - * A single UDP port - */ -export class UdpPort implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.port); - - constructor(private readonly port: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Udp, - fromPort: this.port, - toPort: this.port - }; - } - - public toString() { - const port = Token.isUnresolved(this.port) ? '{IndirectPort}' : this.port; - return `UDP ${port}`; - } -} - -/** - * A UDP port range - */ -export class UdpPortRange implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.startPort) && !Token.isUnresolved(this.endPort); - - constructor(private readonly startPort: number, private readonly endPort: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Udp, - fromPort: this.startPort, - toPort: this.endPort - }; - } - - public toString() { - return `UDP ${this.startPort}-${this.endPort}`; - } -} - -/** - * All UDP Ports - */ -export class UdpAllPorts implements IPortRange { - public readonly canInlineRule = true; - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Udp, - fromPort: 0, - toPort: 65535 - }; - } - - public toString() { - return 'UDP ALL PORTS'; - } -} - -/** - * A set of matching ICMP Type & Code - */ -export class IcmpTypeAndCode implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.type) && !Token.isUnresolved(this.code); - - constructor(private readonly type: number, private readonly code: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Icmp, - fromPort: this.type, - toPort: this.code - }; - } - - public toString() { - return `ICMP Type ${this.type} Code ${this.code}`; - } -} - -/** - * ICMP Ping traffic - */ -export class IcmpPing implements IPortRange { - public readonly canInlineRule = true; - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Icmp, - fromPort: 8, - toPort: -1 - }; - } - - public toString() { - return `ICMP PING`; - } -} - -/** - * All ICMP Codes for a given ICMP Type - */ -export class IcmpAllTypeCodes implements IPortRange { - public readonly canInlineRule = !Token.isUnresolved(this.type); - - constructor(private readonly type: number) { - } - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Icmp, - fromPort: this.type, - toPort: -1 - }; - } - - public toString() { - return `ICMP Type ${this.type}`; - } -} - -/** - * All ICMP Types & Codes - */ -export class IcmpAllTypesAndCodes implements IPortRange { - public readonly canInlineRule = true; - - public toRuleJSON(): any { - return { - ipProtocol: Protocol.Icmp, - fromPort: -1, - toPort: -1 - }; - } - - public toString() { - return 'ALL ICMP'; - } -} - -/** - * All Traffic - */ -export class AllTraffic implements IPortRange { - public readonly canInlineRule = true; - - public toRuleJSON(): any { - return { - ipProtocol: '-1', - }; - } - - public toString() { - return 'ALL TRAFFIC'; - } -} diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 8bd0948c75d0e..eaaa1cb8d54fc 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,12 +1,13 @@ -import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/cdk'; -import { Connections, IConnectable } from './connections'; +import { Construct, IResource, Lazy, PhysicalName, Resource, ResourceProps, Stack } from '@aws-cdk/cdk'; +import { Connections } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; -import { IPortRange, ISecurityGroupRule } from './security-group-rule'; +import { IPeer } from './peer'; +import { Port } from './port'; import { IVpc } from './vpc'; const SECURITY_GROUP_SYMBOL = Symbol.for('@aws-cdk/iam.SecurityGroup'); -export interface ISecurityGroup extends IResource, ISecurityGroupRule, IConnectable { +export interface ISecurityGroup extends IResource, IPeer { /** * ID for the current security group * @attribute @@ -22,7 +23,7 @@ export interface ISecurityGroup extends IResource, ISecurityGroupRule, IConnecta * peer is also a SecurityGroup, the rule object is created under the remote * SecurityGroup object. */ - addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean): void; + addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean): void; /** * Add an egress rule for the current security group @@ -33,7 +34,7 @@ export interface ISecurityGroup extends IResource, ISecurityGroupRule, IConnecta * peer is also a SecurityGroup, the rule object is created under the remote * SecurityGroup object. */ - addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean): void; + addEgressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean): void; } /** @@ -51,14 +52,10 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { public readonly canInlineRule = false; public readonly connections: Connections = new Connections({ securityGroups: [this] }); + public readonly defaultPort?: Port; - /** - * FIXME: Where to place this?? - */ - public readonly defaultPortRange?: IPortRange; - - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string, props?: ResourceProps) { + super(scope, id, props); Object.defineProperty(this, SECURITY_GROUP_SYMBOL, { value: true }); } @@ -67,7 +64,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { return this.node.uniqueId; } - public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean) { + public addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { if (description === undefined) { description = `from ${peer.uniqueId}:${connection}`; } @@ -78,14 +75,14 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { if (scope.node.tryFindChild(id) === undefined) { new CfnSecurityGroupIngress(scope, id, { groupId: this.securityGroupId, - ...peer.toIngressRuleJSON(), + ...peer.toIngressRuleConfig(), ...connection.toRuleJSON(), description }); } } - public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean) { + public addEgressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { if (description === undefined) { description = `to ${peer.uniqueId}:${connection}`; } @@ -96,18 +93,18 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { if (scope.node.tryFindChild(id) === undefined) { new CfnSecurityGroupEgress(scope, id, { groupId: this.securityGroupId, - ...peer.toEgressRuleJSON(), + ...peer.toEgressRuleConfig(), ...connection.toRuleJSON(), description }); } } - public toIngressRuleJSON(): any { + public toIngressRuleConfig(): any { return { sourceSecurityGroupId: this.securityGroupId }; } - public toEgressRuleJSON(): any { + public toEgressRuleConfig(): any { return { destinationSecurityGroupId: this.securityGroupId }; } } @@ -162,8 +159,8 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { */ function determineRuleScope( group: SecurityGroupBase, - peer: ISecurityGroupRule, - connection: IPortRange, + peer: IPeer, + connection: Port, fromTo: 'from' | 'to', remoteRule?: boolean): [SecurityGroupBase, string] { @@ -192,7 +189,7 @@ export interface SecurityGroupProps { * @default If you don't specify a GroupName, AWS CloudFormation generates a * unique physical ID and uses that ID for the group name. */ - readonly groupName?: string; + readonly securityGroupName?: PhysicalName; /** * A description of the security group. @@ -266,14 +263,16 @@ export class SecurityGroup extends SecurityGroupBase { private readonly allowAllOutbound: boolean; constructor(scope: Construct, id: string, props: SecurityGroupProps) { - super(scope, id); + super(scope, id, { + physicalName: props.securityGroupName + }); const groupDescription = props.description || this.node.path; this.allowAllOutbound = props.allowAllOutbound !== false; this.securityGroup = new CfnSecurityGroup(this, 'Resource', { - groupName: props.groupName, + groupName: this.physicalName, groupDescription, securityGroupIngress: Lazy.anyValue({ produce: () => this.directIngressRules }), securityGroupEgress: Lazy.anyValue({ produce: () => this.directEgressRules }), @@ -282,12 +281,12 @@ export class SecurityGroup extends SecurityGroupBase { this.securityGroupId = this.securityGroup.attrGroupId; this.securityGroupVpcId = this.securityGroup.attrVpcId; - this.securityGroupName = this.securityGroup.refAsString; + this.securityGroupName = this.securityGroup.ref; this.addDefaultEgressRule(); } - public addIngressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean) { + public addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { if (!peer.canInlineRule || !connection.canInlineRule) { super.addIngressRule(peer, connection, description, remoteRule); return; @@ -298,13 +297,13 @@ export class SecurityGroup extends SecurityGroupBase { } this.addDirectIngressRule({ - ...peer.toIngressRuleJSON(), + ...peer.toIngressRuleConfig(), ...connection.toRuleJSON(), description }); } - public addEgressRule(peer: ISecurityGroupRule, connection: IPortRange, description?: string, remoteRule?: boolean) { + public addEgressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { if (this.allowAllOutbound) { // In the case of "allowAllOutbound", we don't add any more rules. There // is only one rule which allows all traffic and that subsumes any other @@ -327,7 +326,7 @@ export class SecurityGroup extends SecurityGroupBase { } const rule = { - ...peer.toEgressRuleJSON(), + ...peer.toEgressRuleConfig(), ...connection.toRuleJSON(), description }; diff --git a/packages/@aws-cdk/aws-ec2/lib/user-data.ts b/packages/@aws-cdk/aws-ec2/lib/user-data.ts new file mode 100644 index 0000000000000..47120a3aca801 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/user-data.ts @@ -0,0 +1,82 @@ +import { OperatingSystemType } from "./machine-image"; + +/** + * Options when constructing UserData for Linux + */ +export interface LinuxUserDataOptions { + /** + * Shebang for the UserData script + * + * @default "#!/bin/bash" + */ + readonly shebang?: string; +} + +/** + * Instance User Data + */ +export abstract class UserData { + /** + * Create a userdata object for Linux hosts + */ + public static forLinux(options: LinuxUserDataOptions = {}): UserData { + return new LinuxUserData(options); + } + + /** + * Create a userdata object for Windows hosts + */ + public static forWindows(): UserData { + return new WindowsUserData(); + } + + public static forOperatingSystem(os: OperatingSystemType): UserData { + switch (os) { + case OperatingSystemType.LINUX: return UserData.forLinux(); + case OperatingSystemType.WINDOWS: return UserData.forWindows(); + } + } + + /** + * Add one or more commands to the user data + */ + public abstract addCommands(...commands: string[]): void; + + /** + * Render the UserData for use in a construct + */ + public abstract render(): string; +} + +class LinuxUserData extends UserData { + private readonly lines: string[] = []; + + constructor(private readonly props: LinuxUserDataOptions = {}) { + super(); + } + + public addCommands(...commands: string[]) { + this.lines.push(...commands); + } + + public render(): string { + const shebang = this.props.shebang !== undefined ? this.props.shebang : '#!/bin/bash'; + return [shebang, ...this.lines].join('\n'); + } +} + +class WindowsUserData extends UserData { + private readonly lines: string[] = []; + + constructor() { + super(); + } + + public addCommands(...commands: string[]) { + this.lines.push(...commands); + } + + public render(): string { + return `${this.lines.join('\n')}`; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index ee28b8646f90e..6dc48af4350d5 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -1,5 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { ISubnet, Subnet, SubnetType } from './vpc'; +import { ISubnet, SelectedSubnets, Subnet, SubnetType } from './vpc'; /** * Turn an arbitrary string into one that can be used as a CloudFormation identifier by stripping special characters @@ -15,9 +15,9 @@ export function slugify(x: string): string { */ export function defaultSubnetName(type: SubnetType) { switch (type) { - case SubnetType.Public: return 'Public'; - case SubnetType.Private: return 'Private'; - case SubnetType.Isolated: return 'Isolated'; + case SubnetType.PUBLIC: return 'Public'; + case SubnetType.PRIVATE: return 'Private'; + case SubnetType.ISOLATED: return 'Isolated'; } } @@ -106,3 +106,18 @@ export function range(n: number): number[] { } return ret; } + +/** + * Return the union of table IDs from all selected subnets + */ +export function allRouteTableIds(...ssns: SelectedSubnets[]): string[] { + const ret = new Set(); + for (const ssn of ssns) { + for (const subnet of ssn.subnets) { + if (subnet.routeTable) { + ret.add(subnet.routeTable.routeTableId); + } + } + } + return Array.from(ret); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index 9582322ca133d..05d28d1825a6c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -2,8 +2,9 @@ import iam = require('@aws-cdk/aws-iam'); import { Aws, Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; +import { Port } from './port'; import { SecurityGroup } from './security-group'; -import { TcpPort } from './security-group-rule'; +import { allRouteTableIds } from './util'; import { IVpc, SubnetSelection, SubnetType } from './vpc'; /** @@ -61,7 +62,7 @@ export enum VpcEndpointType { * address that serves as an entry point for traffic destined to a supported * service. */ - Interface = 'Interface', + INTERFACE = 'Interface', /** * Gateway @@ -69,7 +70,7 @@ export enum VpcEndpointType { * A gateway endpoint is a gateway that is a target for a specified route in * your route table, used for traffic destined to a supported AWS service. */ - Gateway = 'Gateway' + GATEWAY = 'Gateway' } /** @@ -164,8 +165,8 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi constructor(scope: Construct, id: string, props: GatewayVpcEndpointProps) { super(scope, id); - const subnets = props.subnets || [{ subnetType: SubnetType.Private }]; - const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectSubnets(s).routeTableIds)))]; + const subnets = props.subnets || [{ subnetType: SubnetType.PRIVATE }]; + const routeTableIds = allRouteTableIds(...subnets.map(s => props.vpc.selectSubnets(s))); if (routeTableIds.length === 0) { throw new Error(`Can't add a gateway endpoint to VPC; route table IDs are not available`); @@ -175,11 +176,11 @@ export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoi policyDocument: Lazy.anyValue({ produce: () => this.policyDocument }), routeTableIds, serviceName: props.service.name, - vpcEndpointType: VpcEndpointType.Gateway, + vpcEndpointType: VpcEndpointType.GATEWAY, vpcId: props.vpc.vpcId }); - this.vpcEndpointId = endpoint.refAsString; + this.vpcEndpointId = endpoint.ref; this.vpcEndpointCreationTimestamp = endpoint.attrCreationTimestamp; this.vpcEndpointDnsEntries = endpoint.attrDnsEntries; this.vpcEndpointNetworkInterfaceIds = endpoint.attrNetworkInterfaceIds; @@ -314,7 +315,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn public readonly vpcEndpointId = attrs.vpcEndpointId; public readonly securityGroupId = attrs.securityGroupId; public readonly connections = new Connections({ - defaultPortRange: new TcpPort(attrs.port), + defaultPort: Port.tcp(attrs.port), securityGroups: [SecurityGroup.fromSecurityGroupId(this, 'SecurityGroup', attrs.securityGroupId)], }); } @@ -364,7 +365,7 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn }); this.securityGroupId = securityGroup.securityGroupId; this.connections = new Connections({ - defaultPortRange: new TcpPort(props.service.port), + defaultPort: Port.tcp(props.service.port), securityGroups: [securityGroup] }); @@ -376,12 +377,12 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn policyDocument: Lazy.anyValue({ produce: () => this.policyDocument }), securityGroupIds: [this.securityGroupId], serviceName: props.service.name, - vpcEndpointType: VpcEndpointType.Interface, + vpcEndpointType: VpcEndpointType.INTERFACE, subnetIds, vpcId: props.vpc.vpcId }); - this.vpcEndpointId = endpoint.refAsString; + this.vpcEndpointId = endpoint.ref; this.vpcEndpointCreationTimestamp = endpoint.attrCreationTimestamp; this.vpcEndpointDnsEntries = endpoint.attrDnsEntries; this.vpcEndpointNetworkInterfaceIds = endpoint.attrNetworkInterfaceIds; diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 50006a700a30b..aa5082a4826da 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,19 +1,10 @@ -import { - ConcreteDependable, - Construct, - ContextProvider, - DependableTrait, - IConstruct, - IDependable, - IResource, - Resource, - Stack, - Tag } from '@aws-cdk/cdk'; +import { ConcreteDependable, Construct, ContextProvider, DependableTrait, IConstruct, + IDependable, IResource, Resource, Stack, Tag } from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, CfnVPNGatewayRoutePropagation } from './ec2.generated'; import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; -import { defaultSubnetName, ImportSubnetGroup, subnetId, subnetName } from './util'; +import { allRouteTableIds, defaultSubnetName, ImportSubnetGroup, subnetId, subnetName } from './util'; import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, GatewayVpcEndpointOptions } from './vpc-endpoint'; import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpcLookupOptions } from './vpc-lookup'; @@ -38,10 +29,20 @@ export interface ISubnet extends IResource { */ readonly internetConnectivityEstablished: IDependable; + /** + * The route table for this subnet + */ + readonly routeTable?: IRouteTable; +} + +/** + * An absract route table + */ +export interface IRouteTable { /** * Route table ID */ - readonly routeTableId?: string; + readonly routeTableId: string; } export interface IVpc extends IResource { @@ -71,25 +72,14 @@ export interface IVpc extends IResource { */ readonly availabilityZones: string[]; - /** - * Region where this VPC is located - */ - readonly region: string; - /** * Identifier for the VPN gateway */ readonly vpnGatewayId?: string; - /** - * Return IDs of the subnets appropriate for the given selection strategy - * - * Requires that at least one subnet is matched, throws a descriptive - * error message otherwise. - * - * @deprecated Use selectSubnets() instead. + * Dependable that can be depended upon to force internet connectivity established on the VPC */ - selectSubnetIds(selection?: SubnetSelection): string[]; + readonly internetConnectivityEstablished: IDependable; /** * Return information on the subnets appropriate for the given selection strategy @@ -99,11 +89,6 @@ export interface IVpc extends IResource { */ selectSubnets(selection?: SubnetSelection): SelectedSubnets; - /** - * Return whether all of the given subnets are from the VPC's public subnets. - */ - isPublicSubnets(subnetIds: string[]): boolean; - /** * Adds a new VPN connection to this VPC */ @@ -125,7 +110,7 @@ export enum SubnetType { * This can be good for subnets with RDS or * Elasticache endpoints */ - Isolated = 'Isolated', + ISOLATED = 'Isolated', /** * Subnet that routes to the internet, but not vice versa. @@ -139,7 +124,7 @@ export enum SubnetType { * experimental cost conscious accounts or accounts where HA outbound * traffic is not needed. */ - Private = 'Private', + PRIVATE = 'Private', /** * Subnet connected to the Internet @@ -151,7 +136,7 @@ export enum SubnetType { * * Public subnets route outbound traffic via an Internet Gateway. */ - Public = 'Public' + PUBLIC = 'Public' } /** @@ -206,14 +191,19 @@ export interface SelectedSubnets { readonly availabilityZones: string[]; /** - * Route table IDs of each respective subnet + * Dependency representing internet connectivity for these subnets + */ + readonly internetConnectivityEstablished: IDependable; + + /** + * Selected subnet objects */ - readonly routeTableIds: string[]; + readonly subnets: ISubnet[]; /** - * Dependency representing internet connectivity for these subnets + * Whether any of the given subnets are from the VPC's public subnets. */ - readonly internetConnectedDependency: IDependable; + readonly hasPublic: boolean; } /** @@ -254,28 +244,26 @@ abstract class VpcBase extends Resource implements IVpc { /** * Dependencies for internet connectivity */ - public readonly internetDependencies = new Array(); + public abstract readonly internetConnectivityEstablished: IDependable; /** * Dependencies for NAT connectivity */ - public readonly natDependencies = new Array(); - - public selectSubnetIds(selection?: SubnetSelection): string[] { - return this.selectSubnets(selection).subnetIds; - } + protected readonly natDependencies = new Array(); /** * Returns IDs of selected subnets */ public selectSubnets(selection: SubnetSelection = {}): SelectedSubnets { const subnets = this.selectSubnetObjects(selection); + const pubs = new Set(this.publicSubnets); return { subnetIds: subnets.map(s => s.subnetId), availabilityZones: subnets.map(s => s.availabilityZone), - routeTableIds: subnets.map(s => s.routeTableId).filter(notUndefined), // Possibly don't have this information - internetConnectedDependency: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), + internetConnectivityEstablished: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), + subnets, + hasPublic: subnets.some(s => pubs.has(s)) }; } @@ -299,21 +287,6 @@ abstract class VpcBase extends Resource implements IVpc { }); } - /** - * Return whether all of the given subnets are from the VPC's public subnets. - */ - public isPublicSubnets(subnetIds: string[]): boolean { - const pubIds = new Set(this.publicSubnets.map(n => n.subnetId)); - return subnetIds.every(pubIds.has.bind(pubIds)); - } - - /** - * The region where this VPC is defined - */ - public get region(): string { - return Stack.of(this).region; - } - /** * Return the subnets appropriate for the placement strategy */ @@ -326,10 +299,10 @@ abstract class VpcBase extends Resource implements IVpc { subnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); } else { // Select by type subnets = { - [SubnetType.Isolated]: this.isolatedSubnets, - [SubnetType.Private]: this.privateSubnets, - [SubnetType.Public]: this.publicSubnets, - }[selection.subnetType || SubnetType.Private]; + [SubnetType.ISOLATED]: this.isolatedSubnets, + [SubnetType.PRIVATE]: this.privateSubnets, + [SubnetType.PUBLIC]: this.publicSubnets, + }[selection.subnetType || SubnetType.PRIVATE]; if (selection.onePerAz && subnets.length > 0) { // Restrict to at most one subnet group @@ -579,12 +552,12 @@ export enum DefaultInstanceTenancy { /** * Instances can be launched with any tenancy. */ - Default = 'default', + DEFAULT = 'default', /** * Any instance launched into the VPC automatically has dedicated tenancy, unless you launch it with the default tenancy. */ - Dedicated = 'dedicated' + DEDICATED = 'dedicated' } /** @@ -631,6 +604,7 @@ export interface SubnetConfiguration { * VpcNetwork deploys an AWS VPC, with public and private subnets per Availability Zone. * For example: * + * ```ts * import { Vpc } from '@aws-cdk/aws-ec2' * * const vpc = new Vpc(this, { @@ -646,6 +620,7 @@ export interface SubnetConfiguration { * for (let subnet of vpc.privateSubnets) { * * } + * ``` * * @resource AWS::EC2::VPC */ @@ -664,12 +639,12 @@ export class Vpc extends VpcBase { */ public static readonly DEFAULT_SUBNETS: SubnetConfiguration[] = [ { - subnetType: SubnetType.Public, - name: defaultSubnetName(SubnetType.Public), + subnetType: SubnetType.PUBLIC, + name: defaultSubnetName(SubnetType.PUBLIC), }, { - subnetType: SubnetType.Private, - name: defaultSubnetName(SubnetType.Private), + subnetType: SubnetType.PRIVATE, + name: defaultSubnetName(SubnetType.PRIVATE), } ]; @@ -757,6 +732,8 @@ export class Vpc extends VpcBase { */ public readonly vpnGatewayId?: string; + public readonly internetConnectivityEstablished: IDependable; + /** * The VPC resource */ @@ -777,6 +754,8 @@ export class Vpc extends VpcBase { */ private subnetConfiguration: SubnetConfiguration[] = []; + private readonly _internetConnectivityEstablished = new ConcreteDependable(); + /** * VpcNetwork creates a VPC that spans a whole region. * It will automatically divide the provided VPC CIDR range, and create public and private subnets per Availability Zone. @@ -799,6 +778,7 @@ export class Vpc extends VpcBase { const enableDnsHostnames = props.enableDnsHostnames == null ? true : props.enableDnsHostnames; const enableDnsSupport = props.enableDnsSupport == null ? true : props.enableDnsSupport; const instanceTenancy = props.defaultInstanceTenancy || 'default'; + this.internetConnectivityEstablished = this._internetConnectivityEstablished; // Define a VPC using the provided CIDR range this.resource = new CfnVPC(this, 'Resource', { @@ -821,27 +801,27 @@ export class Vpc extends VpcBase { const maxAZs = props.maxAZs !== undefined ? props.maxAZs : 3; this.availabilityZones = this.availabilityZones.slice(0, maxAZs); - this.vpcId = this.resource.refAsString; + this.vpcId = this.resource.ref; this.subnetConfiguration = ifUndefined(props.subnetConfiguration, Vpc.DEFAULT_SUBNETS); // subnetConfiguration and natGateways must be set before calling createSubnets this.createSubnets(); const allowOutbound = this.subnetConfiguration.filter( - subnet => (subnet.subnetType !== SubnetType.Isolated)).length > 0; + subnet => (subnet.subnetType !== SubnetType.ISOLATED)).length > 0; // Create an Internet Gateway and attach it if necessary if (allowOutbound) { const igw = new CfnInternetGateway(this, 'IGW', { }); - this.internetDependencies.push(igw); + this._internetConnectivityEstablished.add(igw); const att = new CfnVPCGatewayAttachment(this, 'VPCGW', { - internetGatewayId: igw.refAsString, - vpcId: this.resource.refAsString + internetGatewayId: igw.ref, + vpcId: this.resource.ref }); (this.publicSubnets as PublicSubnet[]).forEach(publicSubnet => { - publicSubnet.addDefaultInternetRoute(igw.refAsString, att); + publicSubnet.addDefaultInternetRoute(igw.ref, att); }); // if gateways are needed create them @@ -865,19 +845,19 @@ export class Vpc extends VpcBase { if (props.vpnGateway || props.vpnConnections || props.vpnGatewayAsn) { const vpnGateway = new CfnVPNGateway(this, 'VpnGateway', { amazonSideAsn: props.vpnGatewayAsn, - type: VpnConnectionType.IPsec1 + type: VpnConnectionType.IPSEC_1 }); const attachment = new CfnVPCGatewayAttachment(this, 'VPCVPNGW', { vpcId: this.vpcId, - vpnGatewayId: vpnGateway.refAsString, + vpnGatewayId: vpnGateway.ref, }); - this.vpnGatewayId = vpnGateway.refAsString; + this.vpnGatewayId = vpnGateway.ref; // Propagate routes on route tables associated with the right subnets - const vpnRoutePropagation = props.vpnRoutePropagation || [{ subnetType: SubnetType.Private }]; - const routeTableIds = [...new Set(Array().concat(...vpnRoutePropagation.map(s => this.selectSubnets(s).routeTableIds)))]; + const vpnRoutePropagation = props.vpnRoutePropagation || [{ subnetType: SubnetType.PRIVATE }]; + const routeTableIds = allRouteTableIds(...vpnRoutePropagation.map(s => this.selectSubnets(s))); const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { routeTableIds, vpnGatewayId: this.vpnGatewayId @@ -938,7 +918,7 @@ export class Vpc extends VpcBase { private createNatGateways(gateways?: number, placement?: SubnetSelection): void { const useNatGateway = this.subnetConfiguration.filter( - subnet => (subnet.subnetType === SubnetType.Private)).length > 0; + subnet => (subnet.subnetType === SubnetType.PRIVATE)).length > 0; const natCount = ifUndefined(gateways, useNatGateway ? this.availabilityZones.length : 0); @@ -959,7 +939,7 @@ export class Vpc extends VpcBase { natSubnets = natSubnets.slice(0, natCount); for (const sub of natSubnets) { const gateway = sub.addNatGateway(); - this.natGatewayByAZ[sub.availabilityZone] = gateway.refAsString; + this.natGatewayByAZ[sub.availabilityZone] = gateway.ref; this.natDependencies.push(gateway); } } @@ -999,22 +979,22 @@ export class Vpc extends VpcBase { availabilityZone: zone, vpcId: this.vpcId, cidrBlock: this.networkBuilder.addSubnet(cidrMask), - mapPublicIpOnLaunch: (subnetConfig.subnetType === SubnetType.Public), + mapPublicIpOnLaunch: (subnetConfig.subnetType === SubnetType.PUBLIC), }; let subnet: Subnet; switch (subnetConfig.subnetType) { - case SubnetType.Public: + case SubnetType.PUBLIC: const publicSubnet = new PublicSubnet(this, name, subnetProps); this.publicSubnets.push(publicSubnet); subnet = publicSubnet; break; - case SubnetType.Private: + case SubnetType.PRIVATE: const privateSubnet = new PrivateSubnet(this, name, subnetProps); this.privateSubnets.push(privateSubnet); subnet = privateSubnet; break; - case SubnetType.Isolated: + case SubnetType.ISOLATED: const isolatedSubnet = new PrivateSubnet(this, name, subnetProps); this.isolatedSubnets.push(isolatedSubnet); subnet = isolatedSubnet; @@ -1036,9 +1016,9 @@ const SUBNETNAME_TAG = 'aws-cdk:subnet-name'; function subnetTypeTagValue(type: SubnetType) { switch (type) { - case SubnetType.Public: return 'Public'; - case SubnetType.Private: return 'Private'; - case SubnetType.Isolated: return 'Isolated'; + case SubnetType.PUBLIC: return 'Public'; + case SubnetType.PRIVATE: return 'Private'; + case SubnetType.ISOLATED: return 'Isolated'; } } @@ -1123,9 +1103,11 @@ export class Subnet extends Resource implements ISubnet { /** * The routeTableId attached to this subnet. */ - public readonly routeTableId?: string; + public readonly routeTable: IRouteTable; - private readonly internetDependencies = new ConcreteDependable(); + public readonly internetConnectivityEstablished: IDependable; + + private readonly _internetConnectivityEstablished = new ConcreteDependable(); constructor(scope: Construct, id: string, props: SubnetProps) { super(scope, id); @@ -1141,7 +1123,7 @@ export class Subnet extends Resource implements ISubnet { availabilityZone: props.availabilityZone, mapPublicIpOnLaunch: props.mapPublicIpOnLaunch, }); - this.subnetId = subnet.refAsString; + this.subnetId = subnet.ref; this.subnetVpcId = subnet.attrVpcId; this.subnetAvailabilityZone = subnet.attrAvailabilityZone; this.subnetIpv6CidrBlocks = subnet.attrIpv6CidrBlocks; @@ -1150,17 +1132,15 @@ export class Subnet extends Resource implements ISubnet { const table = new CfnRouteTable(this, 'RouteTable', { vpcId: props.vpcId, }); - this.routeTableId = table.refAsString; + this.routeTable = { routeTableId: table.ref }; // Associate the public route table for this subnet, to this subnet new CfnSubnetRouteTableAssociation(this, 'RouteTableAssociation', { subnetId: this.subnetId, - routeTableId: table.refAsString + routeTableId: table.ref }); - } - public get internetConnectivityEstablished(): IDependable { - return this.internetDependencies; + this.internetConnectivityEstablished = this._internetConnectivityEstablished; } /** @@ -1172,7 +1152,7 @@ export class Subnet extends Resource implements ISubnet { */ public addDefaultInternetRoute(gatewayId: string, gatewayAttachment: IDependable) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId!, + routeTableId: this.routeTable.routeTableId, destinationCidrBlock: '0.0.0.0/0', gatewayId }); @@ -1180,7 +1160,7 @@ export class Subnet extends Resource implements ISubnet { // Since the 'route' depends on the gateway attachment, just // depending on the route is enough. - this.internetDependencies.add(route); + this._internetConnectivityEstablished.add(route); } /** @@ -1189,11 +1169,11 @@ export class Subnet extends Resource implements ISubnet { */ public addDefaultNatRoute(natGatewayId: string) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId!, + routeTableId: this.routeTable.routeTableId, destinationCidrBlock: '0.0.0.0/0', natGatewayId }); - this.internetDependencies.add(route); + this._internetConnectivityEstablished.add(route); } } @@ -1269,6 +1249,7 @@ class ImportedVpc extends VpcBase { public readonly isolatedSubnets: ISubnet[]; public readonly availabilityZones: string[]; public readonly vpnGatewayId?: string; + public readonly internetConnectivityEstablished: IDependable = new ConcreteDependable(); constructor(scope: Construct, id: string, props: VpcAttributes) { super(scope, id); @@ -1278,9 +1259,9 @@ class ImportedVpc extends VpcBase { this.vpnGatewayId = props.vpnGatewayId; // tslint:disable:max-line-length - const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, SubnetType.Public, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames'); - const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, SubnetType.Private, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames'); - const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, SubnetType.Isolated, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames'); + const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames'); + const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, SubnetType.PRIVATE, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames'); + const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, SubnetType.ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames'); // tslint:enable:max-line-length this.publicSubnets = pub.import(this); @@ -1301,7 +1282,7 @@ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { } if (placement.subnetType === undefined && placement.subnetName === undefined) { - return { subnetType: SubnetType.Private, onePerAz: placement.onePerAz }; + return { subnetType: SubnetType.PRIVATE, onePerAz: placement.onePerAz }; } return placement; @@ -1352,15 +1333,11 @@ function tap(x: T, fn: (x: T) => void): T { return x; } -function notUndefined(x: T | undefined): x is T { - return x !== undefined; -} - class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivateSubnet { public readonly internetConnectivityEstablished: IDependable = new ConcreteDependable(); public readonly availabilityZone: string; public readonly subnetId: string; - public readonly routeTableId?: string = undefined; + public readonly routeTable: IRouteTable; constructor(scope: Construct, id: string, attrs: SubnetAttributes) { super(scope, id); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 63e4a2fd562c0..3517ad371308a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -4,7 +4,7 @@ import net = require('net'); import { CfnCustomerGateway, CfnVPNConnection, CfnVPNConnectionRoute } from './ec2.generated'; import { IVpc } from './vpc'; -export interface IVpnConnection extends cdk.IConstruct { +export interface IVpnConnection extends cdk.IResource { /** * The id of the VPN connection. */ @@ -89,16 +89,21 @@ export enum VpnConnectionType { /** * The IPsec 1 VPN connection type. */ - IPsec1 = 'ipsec.1', + IPSEC_1 = 'ipsec.1', /** * Dummy member * TODO: remove once https://github.com/awslabs/jsii/issues/231 is fixed */ - Dummy = 'dummy' + DUMMY = 'dummy' } -export class VpnConnection extends cdk.Construct implements IVpnConnection { +/** + * Define a VPN Connection + * + * @resource AWS::EC2::VPNConnection + */ +export class VpnConnection extends cdk.Resource implements IVpnConnection { /** * Return the given named metric for all VPN connections in the account/region. */ @@ -116,7 +121,7 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { * @default average over 5 minutes */ public static metricAllTunnelState(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metricAll('TunnelSate', { statistic: 'avg', ...props }); + return this.metricAll('TunnelState', { statistic: 'avg', ...props }); } /** @@ -153,7 +158,7 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { throw new Error(`The \`ip\` ${props.ip} is not a valid IPv4 address.`); } - const type = VpnConnectionType.IPsec1; + const type = VpnConnectionType.IPSEC_1; const bgpAsn = props.asn || 65000; const customerGateway = new CfnCustomerGateway(this, 'CustomerGateway', { @@ -162,7 +167,7 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { type }); - this.customerGatewayId = customerGateway.refAsString; + this.customerGatewayId = customerGateway.ref; this.customerGatewayAsn = bgpAsn; this.customerGatewayIp = props.ip; @@ -199,13 +204,13 @@ export class VpnConnection extends cdk.Construct implements IVpnConnection { const vpnConnection = new CfnVPNConnection(this, 'Resource', { type, - customerGatewayId: customerGateway.refAsString, + customerGatewayId: customerGateway.ref, staticRoutesOnly: props.staticRoutes ? true : false, vpnGatewayId: props.vpc.vpnGatewayId, vpnTunnelOptionsSpecifications: props.tunnelOptions }); - this.vpnId = vpnConnection.refAsString; + this.vpnId = vpnConnection.ref; if (props.staticRoutes) { props.staticRoutes.forEach(route => { diff --git a/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts b/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts new file mode 100644 index 0000000000000..aa89f4e6a3bed --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/windows-versions.ts @@ -0,0 +1,232 @@ +/** + * The Windows version to use for the WindowsImage + */ +export enum WindowsVersion { + WINDOWS_SERVER_2008_SP2_ENGLISH_64BIT_SQL_2008_SP4_EXPRESS = 'Windows_Server-2008-SP2-English-64Bit-SQL_2008_SP4_Express', + WINDOWS_SERVER_2012_R2_RTM_CHINESE_SIMPLIFIED_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Chinese_Simplified-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_CHINESE_TRADITIONAL_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Chinese_Traditional-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_DUTCH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Dutch-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP2_ENTERPRISE = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Enterprise', + WINDOWS_SERVER_2012_R2_RTM_HUNGARIAN_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Hungarian-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_CORE_CONTAINERS = 'Windows_Server-2016-English-Core-Containers', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP1_WEB = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Web', + WINDOWS_SERVER_2016_GERMAL_FULL_BASE = 'Windows_Server-2016-German-Full-Base', + WINDOWS_SERVER_2003_R2_SP2_LANGUAGE_PACKS_32BIT_BASE = 'Windows_Server-2003-R2_SP2-Language_Packs-32Bit-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2008_R2_SP3_WEB = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Web', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2012_SP4_EXPRESS = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Express', + WINDOWS_SERVER_2012_R2_SP1_PORTUGESE_BRAZIL_64BIT_CORE = 'Windows_Server-2008-R2_SP1-Portuguese_Brazil-64Bit-Core', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP2_STANDARD = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2014_SP2_EXPRESS = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Express', + WINDOWS_SERVER_2012_RTM_ITALIAN_64BIT_BASE = 'Windows_Server-2012-RTM-Italian-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP1_EXPRESS = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Express', + WINDOWS_SERVER_2016_ENGLISH_DEEP_LEARNING = 'Windows_Server-2016-English-Deep-Learning', + WINDOWS_SERVER_2019_ITALIAN_FULL_BASE = 'Windows_Server-2019-Italian-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_KOREAN_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Korean-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP1_EXPRESS = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Express', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP2_WEB = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Web', + WINDOWS_SERVER_2016_JAPANESE_FULL_FQL_2016_SP2_WEB = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Web', + WINDOWS_SERVER_2016_KOREAN_FULL_BASE = 'Windows_Server-2016-Korean-Full-Base', + WINDOWS_SERVER_2016_KOREAN_FULL_SQL_2016_SP2_STANDARD = 'Windows_Server-2016-Korean-Full-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2016_PORTUGESE_PORTUGAL_FULL_BASE = 'Windows_Server-2016-Portuguese_Portugal-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2017_WEB = 'Windows_Server-2019-English-Full-SQL_2017_Web', + WINDOWS_SERVER_2019_FRENCH_FULL_BASE = 'Windows_Server-2019-French-Full-Base', + WINDOWS_SERVER_2019_KOREAN_FULL_BASE = 'Windows_Server-2019-Korean-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_CHINESE_HONG_KONG_SAR_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Chinese_Hong_Kong_SAR-64Bit-Base', + WINDOWS_SERVER_2008_R2_SP1_CHINESE_PRC_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Chinese_PRC-64Bit-Base', + WINDOWS_SERVER_2012_RTM_FRENCH_64BIT_BASE = 'Windows_Server-2012-RTM-French-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_CONTAINERS = 'Windows_Server-2016-English-Full-Containers', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP1_STANDARD = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2016_RUSSIAN_FULL_BASE = 'Windows_Server-2016-Russian-Full-Base', + WINDOWS_SERVER_2019_CHINESE_SIMPLIFIED_FULL_BASE = 'Windows_Server-2019-Chinese_Simplified-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2016_SP2_STANDARD = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2019_HUNGARIAN_FULL_BASE = 'Windows_Server-2019-Hungarian-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2008_R2_SP3_EXPRESS = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Express', + WINDOWS_SERVER_2007_R2_SP1_LANGUAGE_PACKS_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-Base', + WINDOWS_SERVER_2008_SP2_ENGLISH_32BIT_BASE = 'Windows_Server-2008-SP2-English-32Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2012_SP4_ENTERPRISE = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2012_SP4_Enterprise', + WINDOWS_SERVER_2012_RTM_CHINESE_TRADITIONAL_64BIT_BASE = 'Windows_Server-2012-RTM-Chinese_Traditional-64Bit-Base', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2008_R2_SP3_EXPRESS = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Express', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2014_SP2_STANDARD = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Standard', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP2_EXPRESS = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Express', + WINDOWS_SERVER_2016_POLISH_FULL_BASE = 'Windows_Server-2016-Polish-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2016_SP2_WEB = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Web', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP3_STANDARD = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Standard', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP2_EXPRESS = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Express', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_DEEP_LEARNING = 'Windows_Server-2012-R2_RTM-English-Deep-Learning', + WINDOWS_SERVER_2012_R2_RTM_GERMAN_64BIT_BASE = 'Windows_Server-2012-R2_RTM-German-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP1_EXPRESS = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Express', + WINDOWS_SERVER_2012_R2_RTM_RUSSIAN_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Russian-64Bit-Base', + WINDOWS_SERVER_2012_RTM_CHINESE_TRADITIONAL_HONG_KONG_SAR_64BIT_BASE = 'Windows_Server-2012-RTM-Chinese_Traditional_Hong_Kong_SAR-64Bit-Base', + WINDOWS_SERVER_2012_RTM_HUNGARIAN_64BIT_BASE = 'Windows_Server-2012-RTM-Hungarian-64Bit-Base', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP3_STANDARD = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Standard', + WINDOWS_SERVER_2019_ENGLISH_FULL_HYPERV = 'Windows_Server-2019-English-Full-HyperV', + WINDOWS_SERVER_2003_R2_SP2_ENGLISH_64BIT_SQL_2005_SP4_EXPRESS = 'Windows_Server-2003-R2_SP2-English-64Bit-SQL_2005_SP4_Express', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2012_SP4_EXPRESS = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2012_SP4_Express', + WINDOWS_SERVER_2012_RTM_GERMAN_64BIT_BASE = 'Windows_Server-2012-RTM-German-64Bit-Base', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2008_R2_SP3_Standard', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP2_STANDARD = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2017_EXPRESS = 'Windows_Server-2019-English-Full-SQL_2017_Express', + WINDOWS_SERVER_2019_JAPANESE_FULL_BASE = 'Windows_Server-2019-Japanese-Full-Base', + WINDOWS_SERVER_2019_RUSSIAN_FULL_BASE = 'Windows_Server-2019-Russian-Full-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP2_STANDARD = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Standard', + WINDOWS_SERVER_2012_R2_RTM_ITALIAN_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Italian-64Bit-Base', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_BASE = 'Windows_Server-2012-RTM-English-64Bit-Base', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Standard', + WINDOWS_SERVER_2016_ENGLISH_FULL_HYPERV = 'Windows_Server-2016-English-Full-HyperV', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_2019_CHINESE_TRADITIONAL_FULL_BASE = 'Windows_Server-2019-Chinese_Traditional-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_CORE_BASE = 'Windows_Server-2019-English-Core-Base', + WINDOWS_SERVER_2019_ENGLISH_CORE_CONTAINERSLATEST = 'Windows_Server-2019-English-Core-ContainersLatest', + WINDOWS_SERVER_2008_SP2_ENGLISH_64BIT_BASE = 'Windows_Server-2008-SP2-English-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_FRENCH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-French-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_POLISH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Polish-64Bit-Base', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2012_SP4_EXPRESS = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Express', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2014_SP3_STANDARD = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Standard', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_2012_SP4_STANDARD = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Standard', + WINDOWS_SERVER_2016_ENGLISH_CORE_CONTAINERSLATEST = 'Windows_Server-2016-English-Core-ContainersLatest', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2016_SP2_EXPRESS = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Express', + WINDOWS_SERVER_2019_TURKISH_FULL_BASE = 'Windows_Server-2019-Turkish-Full-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP2_EXPRESS = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Express', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP3_WEB = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Web', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP1_WEB = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Web', + WINDOWS_SERVER_2012_R2_RTM_PORTUGESE_BRAZIL_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Portuguese_Brazil-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_PORTUGESE_PORTUGAL_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Portuguese_Portugal-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_SWEDISH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Swedish-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP1_EXPRESS = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Express', + WINDOWS_SERVER_2016_ITALIAN_FULL_BASE = 'Windows_Server-2016-Italian-Full-Base', + WINDOWS_SERVER_2016_SPANISH_FULL_BASE = 'Windows_Server-2016-Spanish-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2017_STANDARD = 'Windows_Server-2019-English-Full-SQL_2017_Standard', + WINDOWS_SERVER_2003_R2_SP2_LANGUAGE_PACKS_64BIT_SQL_2005_SP4_STANDARD = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-SQL_2005_SP4_Standard', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Standard', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP1_STANDARD = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2007_R2_SP3_WEB = 'Windows_Server-2012-RTM-English-64Bit-SQL_2008_R2_SP3_Web', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP2_WEB = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Web', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_2016_PORTUGESE_BRAZIL_FULL_BASE = 'Windows_Server-2016-Portuguese_Brazil-Full-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_BASE = 'Windows_Server-2019-English-Full-Base', + WINDOWS_SERVER_2003_R2_SP2_ENGLISH_32BIT_BASE = 'Windows_Server-2003-R2_SP2-English-32Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_CZECH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Czech-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP1_STANDARD = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2014_SP2_EXPRESS = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP2_Express', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2012_SP4_STANDARD = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Standard', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP1_ENTERPRISE = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Enterprise', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP1_WEB = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Web', + WINDOWS_SERVER_2016_SWEDISH_FULL_BASE = 'Windows_Server-2016-Swedish-Full-Base', + WINDOWS_SERVER_2016_TURKISH_FULL_BASE = 'Windows_Server-2016-Turkish-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_CORE_SQL_2012_SP4_STANDARD = 'Windows_Server-2008-R2_SP1-English-64Bit-Core_SQL_2012_SP4_Standard', +// tslint:disable-next-line: max-line-length + WINDOWS_SERVER_2008_R2_SP1_LANGUAGE_PACKS_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-SQL_2008_R2_SP3_Standard', + WINDOWS_SERVER_2012_RTM_CZECH_64BIT_BASE = 'Windows_Server-2012-RTM-Czech-64Bit-Base', + WINDOWS_SERVER_2012_RTM_TURKISH_64BIT_BASE = 'Windows_Server-2012-RTM-Turkish-64Bit-Base', + WINDOWS_SERVER_2016_DUTCH_FULL_BASE = 'Windows_Server-2016-Dutch-Full-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP2_EXPRESS = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Express', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2017_ENTERPRISE = 'Windows_Server-2016-English-Full-SQL_2017_Enterprise', + WINDOWS_SERVER_2016_HUNGARIAN_FULL_BASE = 'Windows_Server-2016-Hungarian-Full-Base', + WINDOWS_SERVER_2016_KOREAN_FULL_SQL_2016_SP1_STANDARD = 'Windows_Server-2016-Korean-Full-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2019_SPANISH_FULL_BASE = 'Windows_Server-2019-Spanish-Full-Base', + WINDOWS_SERVER_2003_R2_SP2_ENGLISH_64BIT_BASE = 'Windows_Server-2003-R2_SP2-English-64Bit-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_BASE = 'Windows_Server-2008-R2_SP1-English-64Bit-Base', + WINDOWS_SERVER_2008_R2_SP1_LANGUAGE_PACKS_64BIT_SQL_2008_R2_SP3_EXPRESS = 'Windows_Server-2008-R2_SP1-Language_Packs-64Bit-SQL_2008_R2_SP3_Express', + WINDOWS_SERVER_2012_SP2_PORTUGESE_BRAZIL_64BIT_BASE = 'Windows_Server-2008-SP2-Portuguese_Brazil-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP1_WEB = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Web', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2014_SP3_EXPRESS = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP3_Express', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_BASE = 'Windows_Server-2012-RTM-Japanese-64Bit-Base', + WINDOWS_SERVER_2019_ENGLISH_FULL_CONTAINERSLATEST = 'Windows_Server-2019-English-Full-ContainersLatest', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2017_ENTERPRISE = 'Windows_Server-2019-English-Full-SQL_2017_Enterprise', + WINDOWS_SERVER_1709_ENGLISH_CORE_CONTAINERSLATEST = 'Windows_Server-1709-English-Core-ContainersLatest', + WINDOWS_SERVER_1803_ENGLISH_CORE_BASE = 'Windows_Server-1803-English-Core-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2012_SP4_WEB = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Web', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-Base', + WINDOWS_SERVER_2008_SP2_ENGLISH_64BIT_SQL_2008_SP4_STANDARD = 'Windows_Server-2008-SP2-English-64Bit-SQL_2008_SP4_Standard', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-English-64Bit-Base', + WINDOWS_SERVER_2012_RTM_PORTUGESE_BRAZIL_64BIT_BASE = 'Windows_Server-2012-RTM-Portuguese_Brazil-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP1_WEB = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Web', + WINDOWS_SERVER_2016_ENGLISH_P3 = 'Windows_Server-2016-English-P3', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP1_ENTERPRISE = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Enterprise', + WINDOWS_SERVER_2003_R2_SP2_LANGUAGE_PACKS_64BIT_BASE = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_CHINESE_TRADITIONAL_HONG_KONG_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Chinese_Traditional_Hong_Kong-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP3_EXPRESS = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Express', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_2012_RTM_CHINESE_SIMPLIFIED_64BIT_BASE = 'Windows_Server-2012-RTM-Chinese_Simplified-64Bit-Base', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2012_SP4_WEB = 'Windows_Server-2012-RTM-English-64Bit-SQL_2012_SP4_Web', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP3_WEB = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Web', + WINDOWS_SERVER_2016_JAPANESE_FULL_BASE = 'Windows_Server-2016-Japanese-Full-Base', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP1_EXPRESS = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Express', + WINDOWS_SERVER_1803_ENGLISH_CORE_CONTAINERSLATEST = 'Windows_Server-1803-English-Core-ContainersLatest', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2012_SP4_STANDARD = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2012_SP4_Standard', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_CORE = 'Windows_Server-2012-R2_RTM-English-64Bit-Core', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP2_WEB = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP2_Web', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2014_SP3_ENTERPRISE = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2014_SP3_Enterprise', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP2_STANDARD = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_2014_SP3_WEB = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Web', + WINDOWS_SERVER_2012_RTM_SWEDISH_64BIT_BASE = 'Windows_Server-2012-RTM-Swedish-64Bit-Base', + WINDOWS_SERVER_2016_CHINESE_SIMPLIFIED_FULL_BASE = 'Windows_Server-2016-Chinese_Simplified-Full-Base', + WINDOWS_SERVER_2019_POLISH_FULL_BASE = 'Windows_Server-2019-Polish-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2008_R2_SP3_WEB = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Web', + WINDOWS_SERVER_2008_R2_SP1_PORTUGESE_BRAZIL_64BIT_BASE = 'Windows_Server-2008-R2_SP1-Portuguese_Brazil-64Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2016_SP1_ENTERPRISE = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP1_Enterprise', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2016_SP2_EXPRESS = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2016_SP2_Express', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2014_SP3_EXPRESS = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP3_Express', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP2_STANDARD = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP2_Standard', + WINDOWS_SERVER_2016_ENGLISH_CORE_BASE = 'Windows_Server-2016-English-Core-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_BASE = 'Windows_Server-2016-English-Full-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2017_WEB = 'Windows_Server-2016-English-Full-SQL_2017_Web', + WINDOWS_SERVER_2019_GERMAN_FULL_BASE = 'Windows_Server-2019-German-Full-Base', + WINDOWS_SERVER_2003_R2_SP2_ENGLISH_64BIT_SQL_2005_SP4_STANDARD = 'Windows_Server-2003-R2_SP2-English-64Bit-SQL_2005_SP4_Standard', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2012_SP4_ENTERPRISE = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Enterprise', + WINDOWS_SERVER_2008_R2_SP1_JAPANESE_64BIT_SQL_2008_R2_SP3_EXPRESS = 'Windows_Server-2008-R2_SP1-Japanese-64Bit-SQL_2008_R2_SP3_Express', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP1_ENTERPRISE = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP1_Enterprise', + WINDOWS_SERVER_2012_RTM_ENGLISH_64BIT_SQL_2014_SP2_WEB = 'Windows_Server-2012-RTM-English-64Bit-SQL_2014_SP2_Web', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2008_R2_SP3_EXPRESS = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2008_R2_SP3_Express', + WINDOWS_SERVER_2016_FRENCH_FULL_BASE = 'Windows_Server-2016-French-Full-Base', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_2019_CZECH_FULL_BASE = 'Windows_Server-2019-Czech-Full-Base', + WINDOWS_SERVER_1809_ENGLISH_CORE_BASE = 'Windows_Server-1809-English-Core-Base', + WINDOWS_SERVER_1809_ENGLISH_CORE_CONTAINERSLATEST = 'Windows_Server-1809-English-Core-ContainersLatest', + WINDOWS_SERVER_2003_R2_SP2_LANGUAGE_PACKS_64BIT_SQL_2005_SP4_EXPRESS = 'Windows_Server-2003-R2_SP2-Language_Packs-64Bit-SQL_2005_SP4_Express', + WINDOWS_SERVER_2012_R2_RTM_TURKISH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Turkish-64Bit-Base', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2012_SP4_WEB = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Web', + WINDOWS_SERVER_2012_RTM_POLISH_64BIT_BASE = 'Windows_Server-2012-RTM-Polish-64Bit-Base', + WINDOWS_SERVER_2012_RTM_SPANISH_64BIT_BASE = 'Windows_Server-2012-RTM-Spanish-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP1_ENTERPRISE = 'Windows_Server-2016-English-Full-SQL_2016_SP1_Enterprise', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP2_EXPRESS = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Express', + WINDOWS_SERVER_2019_ENGLISH_FULL_SQL_2016_SP2_ENTERPRISE = 'Windows_Server-2019-English-Full-SQL_2016_SP2_Enterprise', + WINDOWS_SERVER_1709_ENGLISH_CORE_BASE = 'Windows_Server-1709-English-Core-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_61BIT_SQL_2012_RTM_SP2_ENTERPRISE = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_RTM_SP2_Enterprise', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2012_SP4_STANDARD = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2012_SP4_Standard', + WINDOWS_SERVER_2008_SP2_PORTUGESE_BRAZIL_32BIT_BASE = 'Windows_Server-2008-SP2-Portuguese_Brazil-32Bit-Base', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2014_SP2_STANDARD = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP2_Standard', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2012_SP4_EXPRESS = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2012_SP4_Express', + WINDOWS_SERVER_2012_RTM_PORTUGESE_PORTUGAL_64BIT_BASE = 'Windows_Server-2012-RTM-Portuguese_Portugal-64Bit-Base', + WINDOWS_SERVER_2016_CZECH_FULL_BASE = 'Windows_Server-2016-Czech-Full-Base', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP1_STANDARD = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2019_DUTCH_FULL_BASE = 'Windows_Server-2019-Dutch-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_CORE = 'Windows_Server-2008-R2_SP1-English-64Bit-Core', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_SQL_2016_SP2_WEB = 'Windows_Server-2012-R2_RTM-English-64Bit-SQL_2016_SP2_Web', + WINDOWS_SERVER_2012_R2_RTM_KOREAN_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Korean-64Bit-Base', + WINDOWS_SERVER_2012_RTM_DUTCH_64BIT_BASE = 'Windows_Server-2012-RTM-Dutch-64Bit-Base', + WINDOWS_SERVER_2016_ENGLISH_64BIT_SQL_2012_SP4_ENTERPRISE = 'Windows_Server-2016-English-64Bit-SQL_2012_SP4_Enterprise', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP1_STANDARD = 'Windows_Server-2016-English-Core-SQL_2016_SP1_Standard', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP2_EXPRESS = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Express', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP2_WEB = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Web', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2017_STANDARD = 'Windows_Server-2016-English-Full-SQL_2017_Standard', + WINDOWS_SERVER_2019_PORTUGESE_BRAZIL_FULL_BASE = 'Windows_Server-2019-Portuguese_Brazil-Full-Base', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SQL_2008_R2_SP3_STANDARD = 'Windows_Server-2008-R2_SP1-English-64Bit-SQL_2008_R2_SP3_Standard', + WINDOWS_SERVER_2008_R2_SP1_ENGLISH_64BIT_SHAREPOINT_2010_SP2_FOUNDATION = 'Windows_Server-2008-R2_SP1-English-64Bit-SharePoint_2010_SP2_Foundation', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_P3 = 'Windows_Server-2012-R2_RTM-English-P3', + WINDOWS_SERVER_2012_R2_RTM_JAPANESE_64BIT_SQL_2014_SP3_STANDARD = 'Windows_Server-2012-R2_RTM-Japanese-64Bit-SQL_2014_SP3_Standard', + WINDOWS_SERVER_2012_R2_RTM_SPANISH_64BIT_BASE = 'Windows_Server-2012-R2_RTM-Spanish-64Bit-Base', + WINDOWS_SERVER_2012_RTM_JAPANESE_64BIT_SQL_2014_SP3_EXPRESS = 'Windows_Server-2012-RTM-Japanese-64Bit-SQL_2014_SP3_Express', + WINDOWS_SERVER_2016_ENGLISH_CORE_SQL_2016_SP2_STANDARD = 'Windows_Server-2016-English-Core-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2016_JAPANESE_FULL_SQL_2016_SP2_STANDARD = 'Windows_Server-2016-Japanese-Full-SQL_2016_SP2_Standard', + WINDOWS_SERVER_2019_PORTUGESE_PORTUGAL_FULL_BASE = 'Windows_Server-2019-Portuguese_Portugal-Full-Base', + WINDOWS_SERVER_2019_SWEDISH_FULL_BASE = 'Windows_Server-2019-Swedish-Full-Base', + WINDOWS_SERVER_2012_R2_RTM_ENGLISH_64BIT_HYPERV = 'Windows_Server-2012-R2_RTM-English-64Bit-HyperV', + WINDOWS_SERVER_2012_RTM_KOREAN_64BIT_BASE = 'Windows_Server-2012-RTM-Korean-64Bit-Base', + WINDOWS_SERVER_2012_RTM_RUSSIAN_64BIT_BASE = 'Windows_Server-2012-RTM-Russian-64Bit-Base', + WINDOWS_SERVER_2016_CHINESE_TRADITIONAL_FULL_BASE = 'Windows_Server-2016-Chinese_Traditional-Full-Base', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2016_SP2_WEB = 'Windows_Server-2016-English-Full-SQL_2016_SP2_Web', + WINDOWS_SERVER_2016_ENGLISH_FULL_SQL_2017_EXPRESS = 'Windows_Server-2016-English-Full-SQL_2017_Express', +} diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index b3095ad095b97..1208c53761a40 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -89,8 +89,15 @@ }, "awslint": { "exclude": [ - "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId" + "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId", + "props-physical-name:@aws-cdk/aws-ec2.VpnConnectionProps", + "props-physical-name:@aws-cdk/aws-ec2.GatewayVpcEndpointProps", + "props-physical-name:@aws-cdk/aws-ec2.PrivateSubnetProps", + "props-physical-name:@aws-cdk/aws-ec2.PublicSubnetProps", + "props-physical-name:@aws-cdk/aws-ec2.SubnetProps", + "props-physical-name:@aws-cdk/aws-ec2.VpcProps", + "props-physical-name:@aws-cdk/aws-ec2.InterfaceVpcEndpointProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/example.images.lit.ts b/packages/@aws-cdk/aws-ec2/test/example.images.lit.ts index 1a43bec421a6a..f11d9148f7a38 100644 --- a/packages/@aws-cdk/aws-ec2/test/example.images.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/example.images.lit.ts @@ -2,15 +2,15 @@ import ec2 = require("../lib"); /// !show // Pick a Windows edition to use -const windows = new ec2.WindowsImage(ec2.WindowsVersion.WindowsServer2019EnglishFullBase); +const windows = new ec2.WindowsImage(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE); // Pick the right Amazon Linux edition. All arguments shown are optional // and will default to these values when omitted. const amznLinux = new ec2.AmazonLinuxImage({ - generation: ec2.AmazonLinuxGeneration.AmazonLinux, - edition: ec2.AmazonLinuxEdition.Standard, + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX, + edition: ec2.AmazonLinuxEdition.STANDARD, virtualization: ec2.AmazonLinuxVirt.HVM, - storage: ec2.AmazonLinuxStorage.GeneralPurpose, + storage: ec2.AmazonLinuxStorage.GENERAL_PURPOSE, }); // For other custom (Linux) images, instantiate a `GenericLinuxImage` with diff --git a/packages/@aws-cdk/aws-ec2/test/export-helper.ts b/packages/@aws-cdk/aws-ec2/test/export-helper.ts deleted file mode 100644 index 978c77d52ee18..0000000000000 --- a/packages/@aws-cdk/aws-ec2/test/export-helper.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { CfnOutput, Construct, StringListCfnOutput } from '@aws-cdk/cdk'; -import { ISubnet, SubnetType, Vpc } from '../lib'; -import { defaultSubnetName, range, subnetName } from '../lib/util'; - -export function exportVpc(vpc: Vpc) { - const pub = new ExportSubnetGroup(vpc, 'PublicSubnetIDs', vpc.publicSubnets, SubnetType.Public, vpc.availabilityZones.length); - const priv = new ExportSubnetGroup(vpc, 'PrivateSubnetIDs', vpc.privateSubnets, SubnetType.Private, vpc.availabilityZones.length); - const iso = new ExportSubnetGroup(vpc, 'IsolatedSubnetIDs', vpc.isolatedSubnets, SubnetType.Isolated, vpc.availabilityZones.length); - - const vpnGatewayId = vpc.vpnGatewayId - ? new CfnOutput(vpc, 'VpnGatewayId', { value: vpc.vpnGatewayId }).makeImportValue().toString() - : undefined; - - return { - vpcId: new CfnOutput(vpc, 'VpcId', { value: vpc.vpcId }).makeImportValue().toString(), - vpnGatewayId, - availabilityZones: vpc.availabilityZones, - publicSubnetIds: pub.ids, - publicSubnetNames: pub.names, - privateSubnetIds: priv.ids, - privateSubnetNames: priv.names, - isolatedSubnetIds: iso.ids, - isolatedSubnetNames: iso.names, - }; -} - -/** - * Helper class to export/import groups of subnets - */ -export class ExportSubnetGroup { - public readonly ids?: string[]; - public readonly names?: string[]; - - private readonly groups: number; - - constructor( - scope: Construct, - exportName: string, - private readonly subnets: ISubnet[], - private readonly type: SubnetType, - private readonly azs: number) { - - this.groups = subnets.length / azs; - - // ASSERTION - if (Math.floor(this.groups) !== this.groups) { - throw new Error(`Number of subnets (${subnets.length}) must be a multiple of number of availability zones (${azs})`); - } - - this.ids = this.exportIds(scope, exportName); - this.names = this.exportNames(); - } - - private exportIds(scope: Construct, name: string): string[] | undefined { - if (this.subnets.length === 0) { return undefined; } - return new StringListCfnOutput(scope, name, { values: this.subnets.map(s => s.subnetId) }).makeImportValues().map(x => x.toString()); - } - - /** - * Return the list of subnet names if they're not equal to the default - */ - private exportNames(): string[] | undefined { - if (this.subnets.length === 0) { return undefined; } - const netNames = this.subnets.map(subnetName); - - // Do some assertion that the 'netNames' array is laid out like this: - // - // [ INGRESS, INGRESS, INGRESS, EGRESS, EGRESS, EGRESS, ... ] - for (let i = 0; i < netNames.length; i++) { - const k = Math.floor(i / this.azs); - if (netNames[i] !== netNames[k * this.azs]) { - throw new Error(`Subnets must be grouped by name, got: ${JSON.stringify(netNames)}`); - } - } - - // Splat down to [ INGRESS, EGRESS, ... ] - const groupNames = range(this.groups).map(i => netNames[i * this.azs]); - if (groupNames.length === 1 && groupNames[0] === defaultSubnetName(this.type)) { return undefined; } - - return groupNames; - } -} diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json index fc6f104af9b79..73f724bf4e225 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc.expected.json @@ -510,7 +510,7 @@ "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", - "Description": "from 0.0.0.0/0:ICMP PING", + "Description": "from 0.0.0.0/0:ICMP Type 8", "FromPort": 8, "IpProtocol": "icmp", "ToPort": -1 @@ -557,4 +557,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts index b10c05f6ac4a9..61eee83200066 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc.ts @@ -10,16 +10,16 @@ const vpc = new ec2.Vpc(stack, 'MyVpc'); const sg = new ec2.SecurityGroup(stack, 'SG', { vpc }); const rules = [ - new ec2.IcmpPing(), - new ec2.IcmpAllTypeCodes(128), - new ec2.IcmpAllTypesAndCodes(), - new ec2.UdpAllPorts(), - new ec2.UdpPort(123), - new ec2.UdpPortRange(800, 801), + ec2.Port.icmpPing(), + ec2.Port.icmpType(128), + ec2.Port.allIcmp(), + ec2.Port.allUdp(), + ec2.Port.udp(123), + ec2.Port.udpRange(800, 801), ]; for (const rule of rules) { - sg.addIngressRule(new ec2.AnyIPv4(), rule); + sg.addIngressRule(ec2.Peer.anyIpv4(), rule); } app.synth(); diff --git a/packages/@aws-cdk/aws-ec2/test/test.connections.ts b/packages/@aws-cdk/aws-ec2/test/test.connections.ts index 91811719f0ed0..e44aee248ad7c 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.connections.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.connections.ts @@ -5,9 +5,8 @@ import { Test } from 'nodeunit'; import { Connections, IConnectable, + Port, SecurityGroup, - TcpAllPorts, - TcpPort, Vpc, } from "../lib"; @@ -24,7 +23,7 @@ export = { const conn2 = new SomethingConnectable(new Connections({ securityGroups: [sg2] })); // WHEN - conn1.connections.allowTo(conn2, new TcpPort(80), 'Test'); + conn1.connections.allowTo(conn2, Port.tcp(80), 'Test'); // THEN -- it finishes! test.done(); @@ -40,7 +39,7 @@ export = { const securityGroup = SecurityGroup.fromSecurityGroupId(stack, 'ImportedSG', 'sg-12345'); // WHEN - somethingConnectable.connections.allowTo(securityGroup, new TcpAllPorts(), 'Connect there'); + somethingConnectable.connections.allowTo(securityGroup, Port.allTcp(), 'Connect there'); // THEN: rule to generated security group to connect to imported expect(stack).to(haveResource("AWS::EC2::SecurityGroupEgress", { @@ -74,7 +73,7 @@ export = { const connections = new Connections({ securityGroups: [sg1] }); // WHEN - connections.allowFromAnyIPv4(new TcpPort(88)); + connections.allowFromAnyIPv4(Port.tcp(88)); connections.addSecurityGroup(sg2); // THEN @@ -119,7 +118,7 @@ export = { const connectable = new SomethingConnectable(connections2); // WHEN - connections1.allowTo(connectable, new TcpPort(88)); + connections1.allowTo(connectable, Port.tcp(88)); connections2.addSecurityGroup(sg3); // THEN @@ -149,7 +148,7 @@ export = { const connections = new Connections({ securityGroups: [sg1] }); // WHEN - connections.allowInternally(new TcpPort(88)); + connections.allowInternally(Port.tcp(88)); connections.addSecurityGroup(sg2); // THEN @@ -176,7 +175,7 @@ export = { const sg2 = new SecurityGroup(stack2, 'SecurityGroup', { vpc: vpc2, allowAllOutbound: false }); // WHEN - sg2.connections.allowFrom(sg1, new TcpPort(100)); + sg2.connections.allowFrom(sg1, Port.tcp(100)); // THEN -- both rules are in Stack2 ConstructNode.prepare(app.node); @@ -207,7 +206,7 @@ export = { const sg2 = new SecurityGroup(stack2, 'SecurityGroup', { vpc: vpc2, allowAllOutbound: false }); // WHEN - sg2.connections.allowTo(sg1, new TcpPort(100)); + sg2.connections.allowTo(sg1, Port.tcp(100)); // THEN -- both rules are in Stack2 ConstructNode.prepare(app.node); @@ -239,8 +238,8 @@ export = { const sg2 = new SecurityGroup(stack2, 'SecurityGroup', { vpc: vpc2, allowAllOutbound: false }); // WHEN - sg2.connections.allowFrom(sg1a, new TcpPort(100)); - sg2.connections.allowFrom(sg1b, new TcpPort(100)); + sg2.connections.allowFrom(sg1a, Port.tcp(100)); + sg2.connections.allowFrom(sg1b, Port.tcp(100)); // THEN -- both egress rules are in Stack2 ConstructNode.prepare(app.node); diff --git a/packages/@aws-cdk/aws-ec2/test/test.security-group.ts b/packages/@aws-cdk/aws-ec2/test/test.security-group.ts index 091c6e46a3d0b..50cf725e5ada2 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.security-group.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.security-group.ts @@ -1,25 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { Lazy, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; - -import { - AllTraffic, - AnyIPv4, - AnyIPv6, - IcmpAllTypeCodes, - IcmpAllTypesAndCodes, - IcmpPing, - IcmpTypeAndCode, - PrefixList, - SecurityGroup, - TcpAllPorts, - TcpPort, - TcpPortRange, - UdpAllPorts, - UdpPort, - UdpPortRange, - Vpc -} from "../lib"; +import { Peer, Port, SecurityGroup, Vpc } from "../lib"; export = { 'security group can allows all outbound traffic by default'(test: Test) { @@ -51,7 +33,7 @@ export = { // WHEN const sg = new SecurityGroup(stack, 'SG1', { vpc, allowAllOutbound: true }); - sg.addEgressRule(new AnyIPv4(), new TcpPort(86), 'This does not show up'); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'This does not show up'); // THEN expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { @@ -98,7 +80,7 @@ export = { // WHEN const sg = new SecurityGroup(stack, 'SG1', { vpc, allowAllOutbound: false }); - sg.addEgressRule(new AnyIPv4(), new TcpPort(86), 'This replaces the other one'); + sg.addEgressRule(Peer.anyIpv4(), Port.tcp(86), 'This replaces the other one'); // THEN expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { @@ -124,7 +106,7 @@ export = { // WHEN const sg = new SecurityGroup(stack, 'SG1', { vpc, allowAllOutbound: false }); test.throws(() => { - sg.addEgressRule(new AnyIPv4(), new AllTraffic(), 'All traffic'); + sg.addEgressRule(Peer.anyIpv4(), Port.allTraffic(), 'All traffic'); }, /Cannot add/); test.done(); @@ -138,25 +120,25 @@ export = { const peers = [ new SecurityGroup(stack, 'PeerGroup', { vpc }), - new AnyIPv4(), - new AnyIPv6(), - new PrefixList('pl-012345'), + Peer.anyIpv4(), + Peer.anyIpv6(), + Peer.prefixList('pl-012345'), ]; const ports = [ - new TcpPort(1234), - new TcpPort(Lazy.numberValue({ produce: () => 5000 })), - new TcpAllPorts(), - new TcpPortRange(80, 90), - new UdpPort(2345), - new UdpPort(Lazy.numberValue({ produce: () => 7777 })), - new UdpAllPorts(), - new UdpPortRange(85, 95), - new IcmpTypeAndCode(5, 1), - new IcmpAllTypeCodes(8), - new IcmpAllTypesAndCodes(), - new IcmpPing(), - new AllTraffic() + Port.tcp(1234), + Port.tcp(Lazy.numberValue({ produce: () => 5000 })), + Port.allTcp(), + Port.tcpRange(80, 90), + Port.udp(2345), + Port.udp(Lazy.numberValue({ produce: () => 7777 })), + Port.allUdp(), + Port.udpRange(85, 95), + Port.icmpTypeAndCode(5, 1), + Port.icmpType(8), + Port.allIcmp(), + Port.icmpPing(), + Port.allTraffic() ]; // WHEN @@ -179,19 +161,19 @@ export = { // WHEN const ports = [ - new TcpPort(p1), - new TcpPort(p2), - new TcpPortRange(p1, 90), - new TcpPortRange(80, p2), - new TcpPortRange(p1, p2), - new UdpPort(p1), - new UdpPortRange(p1, 95), - new UdpPortRange(85, p2), - new UdpPortRange(p1, p2), - new IcmpTypeAndCode(p1, 1), - new IcmpTypeAndCode(5, p1), - new IcmpTypeAndCode(p1, p2), - new IcmpAllTypeCodes(p1), + Port.tcp(p1), + Port.tcp(p2), + Port.tcpRange(p1, 90), + Port.tcpRange(80, p2), + Port.tcpRange(p1, p2), + Port.udp(p1), + Port.udpRange(p1, 95), + Port.udpRange(85, p2), + Port.udpRange(p1, p2), + Port.icmpTypeAndCode(p1, 1), + Port.icmpTypeAndCode(5, p1), + Port.icmpTypeAndCode(p1, p2), + Port.icmpType(p1), ]; // THEN diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts index a30369a980163..2187987045462 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -62,10 +62,10 @@ export = { service: GatewayVpcEndpointAwsService.S3, subnets: [ { - subnetType: SubnetType.Public + subnetType: SubnetType.PUBLIC }, { - subnetType: SubnetType.Private + subnetType: SubnetType.PRIVATE } ] } diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index e0e19f4246443..dcc26d107a3e5 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -1,8 +1,7 @@ import { countResources, expect, haveResource, haveResourceLike, isSuperObject } from '@aws-cdk/assert'; -import { Construct, Stack, Tag } from '@aws-cdk/cdk'; +import { Stack, Tag } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { CfnVPC, DefaultInstanceTenancy, IVpc, SubnetType, Vpc } from '../lib'; -import { exportVpc } from './export-helper'; +import { CfnVPC, DefaultInstanceTenancy, SubnetType, Vpc } from '../lib'; export = { "When creating a VPC": { @@ -22,7 +21,7 @@ export = { CidrBlock: Vpc.DEFAULT_CIDR_RANGE, EnableDnsHostnames: true, EnableDnsSupport: true, - InstanceTenancy: DefaultInstanceTenancy.Default, + InstanceTenancy: DefaultInstanceTenancy.DEFAULT, })); test.done(); }, @@ -48,14 +47,14 @@ export = { cidr: "192.168.0.0/16", enableDnsHostnames: false, enableDnsSupport: false, - defaultInstanceTenancy: DefaultInstanceTenancy.Dedicated, + defaultInstanceTenancy: DefaultInstanceTenancy.DEDICATED, }); expect(stack).to(haveResource('AWS::EC2::VPC', { CidrBlock: '192.168.0.0/16', EnableDnsHostnames: false, EnableDnsSupport: false, - InstanceTenancy: DefaultInstanceTenancy.Dedicated, + InstanceTenancy: DefaultInstanceTenancy.DEDICATED, })); test.done(); }, @@ -75,7 +74,7 @@ export = { new Vpc(stack, 'TheVPC', { subnetConfiguration: [ { - subnetType: SubnetType.Isolated, + subnetType: SubnetType.ISOLATED, name: 'Isolated', } ] @@ -93,11 +92,11 @@ export = { new Vpc(stack, 'TheVPC', { subnetConfiguration: [ { - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, name: 'Public', }, { - subnetType: SubnetType.Isolated, + subnetType: SubnetType.ISOLATED, name: 'Isolated', } ] @@ -123,19 +122,19 @@ export = { subnetConfiguration: [ { cidrMask: 24, - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, name: 'Private', }, { cidrMask: 24, name: 'reserved', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, reserved: true, }, { cidrMask: 28, name: 'rds', - subnetType: SubnetType.Isolated, + subnetType: SubnetType.ISOLATED, } ], maxAZs: 3 @@ -151,18 +150,18 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, }, { cidrMask: 24, name: 'reserved', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, reserved: true, }, { cidrMask: 24, name: 'rds', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, } ], maxAZs: 3 @@ -193,17 +192,17 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'application', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, }, { cidrMask: 28, name: 'rds', - subnetType: SubnetType.Isolated, + subnetType: SubnetType.ISOLATED, } ], maxAZs: 3 @@ -232,17 +231,17 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'application', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, }, { cidrMask: 28, name: 'rds', - subnetType: SubnetType.Isolated, + subnetType: SubnetType.ISOLATED, } ], maxAZs: 3 @@ -278,7 +277,7 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, } ], }); @@ -345,17 +344,17 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'egress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'private', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, }, ], natGatewaySubnets: { @@ -378,12 +377,12 @@ export = { { cidrMask: 24, name: 'ingress', - subnetType: SubnetType.Public, + subnetType: SubnetType.PUBLIC, }, { cidrMask: 24, name: 'private', - subnetType: SubnetType.Private, + subnetType: SubnetType.PRIVATE, }, ], natGatewaySubnets: { @@ -436,13 +435,13 @@ export = { const stack = getTestStack(); new Vpc(stack, 'VPC', { subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: 'Isolated' }, + { subnetType: SubnetType.PRIVATE, name: 'Private' }, + { subnetType: SubnetType.ISOLATED, name: 'Isolated' }, ], vpnGateway: true, vpnRoutePropagation: [ { - subnetType: SubnetType.Isolated + subnetType: SubnetType.ISOLATED } ] }); @@ -470,16 +469,16 @@ export = { const stack = getTestStack(); new Vpc(stack, 'VPC', { subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: 'Isolated' }, + { subnetType: SubnetType.PRIVATE, name: 'Private' }, + { subnetType: SubnetType.ISOLATED, name: 'Isolated' }, ], vpnGateway: true, vpnRoutePropagation: [ { - subnetType: SubnetType.Private + subnetType: SubnetType.PRIVATE }, { - subnetType: SubnetType.Isolated + subnetType: SubnetType.ISOLATED } ] }); @@ -623,7 +622,7 @@ export = { const vpc = new Vpc(stack, 'VPC'); // WHEN - const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Public }); + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.PUBLIC }); // THEN test.deepEqual(subnetIds, vpc.publicSubnets.map(s => s.subnetId)); @@ -636,13 +635,13 @@ export = { const stack = getTestStack(); const vpc = new Vpc(stack, 'VPC', { subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: 'Isolated' }, + { subnetType: SubnetType.PRIVATE, name: 'Private' }, + { subnetType: SubnetType.ISOLATED, name: 'Isolated' }, ] }); // WHEN - const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Isolated }); + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.ISOLATED }); // THEN test.deepEqual(subnetIds, vpc.isolatedSubnets.map(s => s.subnetId)); @@ -655,8 +654,8 @@ export = { const stack = getTestStack(); const vpc = new Vpc(stack, 'VPC', { subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'DontTalkToMe' }, - { subnetType: SubnetType.Isolated, name: 'DontTalkAtAll' }, + { subnetType: SubnetType.PRIVATE, name: 'DontTalkToMe' }, + { subnetType: SubnetType.ISOLATED, name: 'DontTalkAtAll' }, ] }); @@ -690,8 +689,8 @@ export = { const vpc = new Vpc(stack, 'VpcNetwork', { maxAZs: 1, subnetConfiguration: [ - {name: 'app', subnetType: SubnetType.Private }, - {name: 'db', subnetType: SubnetType.Private }, + {name: 'app', subnetType: SubnetType.PRIVATE }, + {name: 'db', subnetType: SubnetType.PRIVATE }, ] }); @@ -704,118 +703,12 @@ export = { test.done(); } }, - - 'export/import': { - 'simple VPC'(test: Test) { - // WHEN - const vpc2 = doImportExportTest(stack => { - return new Vpc(stack, 'TheVPC'); - }); - - // THEN - test.deepEqual(Stack.of(vpc2).resolve(vpc2.vpcId), { - 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' - }); - - test.done(); - }, - - 'multiple subnets of the same type'(test: Test) { - // WHEN - const imported = doImportExportTest(stack => { - return new Vpc(stack, 'TheVPC', { - subnetConfiguration: [ - { name: 'Ingress', subnetType: SubnetType.Public }, - { name: 'Egress', subnetType: SubnetType.Public }, - ] - }); - }); - - // THEN - test.deepEqual(Stack.of(imported).resolve(imported.vpcId), { - 'Fn::ImportValue': 'TestStack:TheVPCVpcIdD346CDBA' - }); - - test.equal(6, imported.publicSubnets.length); - - for (let i = 0; i < 3; i++) { - // tslint:disable-next-line:max-line-length - test.equal(true, imported.publicSubnets[i].node.id.startsWith('Ingress'), `${imported.publicSubnets[i].node.id} does not start with "Ingress"`); - } - for (let i = 3; i < 6; i++) { - // tslint:disable-next-line:max-line-length - test.equal(true, imported.publicSubnets[i].node.id.startsWith('Egress'), `${imported.publicSubnets[i].node.id} does not start with "Egress"`); - } - - test.done(); - }, - - 'can select isolated subnets by type'(test: Test) { - // GIVEN - const importedVpc = doImportExportTest(stack => { - return new Vpc(stack, 'TheVPC', { - subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: 'Isolated' }, - ] - }); - }); - - // WHEN - const { subnetIds } = importedVpc.selectSubnets({ subnetType: SubnetType.Isolated }); - - // THEN - test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); - - test.done(); - }, - - 'can select isolated subnets by name'(test: Test) { - // Do the test with both default name and custom name - for (const isolatedName of ['Isolated', 'LeaveMeAlone']) { - // GIVEN - const importedVpc = doImportExportTest(stack => { - return new Vpc(stack, 'TheVPC', { - subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: isolatedName }, - ] - }); - }); - - // WHEN - const { subnetIds } = importedVpc.selectSubnets({ subnetName: isolatedName }); - - // THEN - test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); - } - - test.done(); - }, - }, - }; function getTestStack(): Stack { return new Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } }); } -/** - * Do a complete import/export test, return the imported VPC - */ -function doImportExportTest(constructFn: (scope: Construct) => Vpc): IVpc { - // GIVEN - const stack1 = getTestStack(); - const stack2 = getTestStack(); - - const vpc1 = constructFn(stack1); - - // WHEN - return Vpc.fromVpcAttributes(stack2, 'VPC2', exportVpc(vpc1)); -} - function toCfnTags(tags: any): Array<{Key: string, Value: string}> { return Object.keys(tags).map( key => { return {Key: key, Value: tags[key]}; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts index 97e6c321ec56d..5e805f6d04c37 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpn.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpn.ts @@ -1,5 +1,5 @@ import { expect, haveResource, } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Vpc, VpnConnection } from '../lib'; @@ -278,7 +278,7 @@ export = { dimensions: { VpnId: { Ref: 'VpcNetworkVpnA476C58D' } }, namespace: 'AWS/VPN', metricName: 'TunnelState', - periodSec: 300, + period: Duration.minutes(5), statistic: 'Average' }); @@ -293,7 +293,7 @@ export = { test.deepEqual(stack.resolve(VpnConnection.metricAllTunnelDataOut()), { namespace: 'AWS/VPN', metricName: 'TunnelDataOut', - periodSec: 300, + period: Duration.minutes(5), statistic: 'Sum' }); diff --git a/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts b/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts index 258af7ac16c64..13e417d40d703 100644 --- a/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts +++ b/packages/@aws-cdk/aws-ecr-assets/lib/adopted-repository.ts @@ -38,7 +38,7 @@ export class AdoptedRepository extends ecr.RepositoryBase { handler: 'handler.handler', code: lambda.Code.asset(path.join(__dirname, 'adopt-repository')), uuid: 'dbc60def-c595-44bc-aa5c-28c95d68f62c', - timeout: 300 + timeout: cdk.Duration.minutes(5) }); fn.addToRolePolicy(new iam.PolicyStatement({ diff --git a/packages/@aws-cdk/aws-ecr-assets/package-lock.json b/packages/@aws-cdk/aws-ecr-assets/package-lock.json index 19a96836c6102..3c27edb8bdf25 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package-lock.json +++ b/packages/@aws-cdk/aws-ecr-assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecr-assets", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts b/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts index d86bb540c7e7b..63468e4f1ae69 100644 --- a/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts +++ b/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts @@ -1,3 +1,5 @@ +import { Duration } from '@aws-cdk/cdk'; + /** * An ECR life cycle rule */ @@ -50,11 +52,11 @@ export interface LifecycleRule { readonly maxImageCount?: number; /** - * The maximum age of images to retain + * The maximum age of images to retain. The value must represent a number of days. * - * Specify exactly one of maxImageCount and maxImageAgeDays. + * Specify exactly one of maxImageCount and maxImageAge. */ - readonly maxImageAgeDays?: number; + readonly maxImageAge?: Duration; } /** @@ -64,17 +66,17 @@ export enum TagStatus { /** * Rule applies to all images */ - Any = 'any', + ANY = 'any', /** * Rule applies to tagged images */ - Tagged = 'tagged', + TAGGED = 'tagged', /** * Rule applies to untagged images */ - Untagged = 'untagged', + UNTAGGED = 'untagged', } /** @@ -84,10 +86,10 @@ export enum CountType { /** * Set a limit on the number of images in your repository */ - ImageCountMoreThan = 'imageCountMoreThan', + IMAGE_COUNT_MORE_THAN = 'imageCountMoreThan', /** * Set an age limit on the images in your repository */ - SinceImagePushed = 'sinceImagePushed', + SINCE_IMAGE_PUSHED = 'sinceImagePushed', } diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 58efbb09ad361..da9fd6fcbf3b8 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,6 +1,6 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); -import { Construct, IConstruct, IResource, Lazy, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; +import { Construct, IConstruct, IResource, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { CfnRepository } from './ecr.generated'; import { CountType, LifecycleRule, TagStatus } from './lifecycle'; @@ -230,7 +230,7 @@ export interface RepositoryProps { * * @default Automatically generated name. */ - readonly repositoryName?: string; + readonly repositoryName?: PhysicalName; /** * Life cycle rules to apply to this registry @@ -335,10 +335,12 @@ export class Repository extends RepositoryBase { private policyDocument?: iam.PolicyDocument; constructor(scope: Construct, id: string, props: RepositoryProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.repositoryName, + }); const resource = new CfnRepository(this, 'Resource', { - repositoryName: props.repositoryName, + repositoryName: this.physicalName, // It says "Text", but they actually mean "Object". repositoryPolicyText: Lazy.anyValue({ produce: () => this.policyDocument }), lifecyclePolicy: Lazy.anyValue({ produce: () => this.renderLifecyclePolicy() }), @@ -351,8 +353,17 @@ export class Repository extends RepositoryBase { props.lifecycleRules.forEach(this.addLifecycleRule.bind(this)); } - this.repositoryName = resource.refAsString; - this.repositoryArn = resource.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.attrArn, + name: resource.ref, + arnComponents: { + service: 'ecr', + resource: 'repository', + resourceName: this.physicalName, + }, + }); + this.repositoryName = resourceIdentifiers.name; + this.repositoryArn = resourceIdentifiers.arn; } public addToResourcePolicy(statement: iam.PolicyStatement) { @@ -371,20 +382,20 @@ export class Repository extends RepositoryBase { public addLifecycleRule(rule: LifecycleRule) { // Validate rule here so users get errors at the expected location if (rule.tagStatus === undefined) { - rule = { ...rule, tagStatus: rule.tagPrefixList === undefined ? TagStatus.Any : TagStatus.Tagged }; + rule = { ...rule, tagStatus: rule.tagPrefixList === undefined ? TagStatus.ANY : TagStatus.TAGGED }; } - if (rule.tagStatus === TagStatus.Tagged && (rule.tagPrefixList === undefined || rule.tagPrefixList.length === 0)) { + if (rule.tagStatus === TagStatus.TAGGED && (rule.tagPrefixList === undefined || rule.tagPrefixList.length === 0)) { throw new Error('TagStatus.Tagged requires the specification of a tagPrefixList'); } - if (rule.tagStatus !== TagStatus.Tagged && rule.tagPrefixList !== undefined) { + if (rule.tagStatus !== TagStatus.TAGGED && rule.tagPrefixList !== undefined) { throw new Error('tagPrefixList can only be specified when tagStatus is set to Tagged'); } - if ((rule.maxImageAgeDays !== undefined) === (rule.maxImageCount !== undefined)) { - throw new Error(`Life cycle rule must contain exactly one of 'maxImageAgeDays' and 'maxImageCount', got: ${JSON.stringify(rule)}`); + if ((rule.maxImageAge !== undefined) === (rule.maxImageCount !== undefined)) { + throw new Error(`Life cycle rule must contain exactly one of 'maxImageAge' and 'maxImageCount', got: ${JSON.stringify(rule)}`); } - if (rule.tagStatus === TagStatus.Any && this.lifecycleRules.filter(r => r.tagStatus === TagStatus.Any).length > 0) { + if (rule.tagStatus === TagStatus.ANY && this.lifecycleRules.filter(r => r.tagStatus === TagStatus.ANY).length > 0) { throw new Error('Life cycle can only have one TagStatus.Any rule'); } @@ -420,9 +431,9 @@ export class Repository extends RepositoryBase { private orderedLifecycleRules(): LifecycleRule[] { if (this.lifecycleRules.length === 0) { return []; } - const prioritizedRules = this.lifecycleRules.filter(r => r.rulePriority !== undefined && r.tagStatus !== TagStatus.Any); - const autoPrioritizedRules = this.lifecycleRules.filter(r => r.rulePriority === undefined && r.tagStatus !== TagStatus.Any); - const anyRules = this.lifecycleRules.filter(r => r.tagStatus === TagStatus.Any); + const prioritizedRules = this.lifecycleRules.filter(r => r.rulePriority !== undefined && r.tagStatus !== TagStatus.ANY); + const autoPrioritizedRules = this.lifecycleRules.filter(r => r.rulePriority === undefined && r.tagStatus !== TagStatus.ANY); + const anyRules = this.lifecycleRules.filter(r => r.tagStatus === TagStatus.ANY); if (anyRules.length > 0 && anyRules[0].rulePriority !== undefined && autoPrioritizedRules.length > 0) { // Supporting this is too complex for very little value. We just prohibit it. throw new Error("Cannot combine prioritized TagStatus.Any rule with unprioritized rules. Remove rulePriority from the 'Any' rule."); @@ -446,7 +457,7 @@ export class Repository extends RepositoryBase { } function validateAnyRuleLast(rules: LifecycleRule[]) { - const anyRules = rules.filter(r => r.tagStatus === TagStatus.Any); + const anyRules = rules.filter(r => r.tagStatus === TagStatus.ANY); if (anyRules.length === 1) { const maxPrio = Math.max(...rules.map(r => r.rulePriority!)); if (anyRules[0].rulePriority !== maxPrio) { @@ -463,11 +474,11 @@ function renderLifecycleRule(rule: LifecycleRule) { rulePriority: rule.rulePriority, description: rule.description, selection: { - tagStatus: rule.tagStatus || TagStatus.Any, + tagStatus: rule.tagStatus || TagStatus.ANY, tagPrefixList: rule.tagPrefixList, - countType: rule.maxImageAgeDays !== undefined ? CountType.SinceImagePushed : CountType.ImageCountMoreThan, - countNumber: rule.maxImageAgeDays !== undefined ? rule.maxImageAgeDays : rule.maxImageCount, - countUnit: rule.maxImageAgeDays !== undefined ? 'days' : undefined, + countType: rule.maxImageAge !== undefined ? CountType.SINCE_IMAGE_PUSHED : CountType.IMAGE_COUNT_MORE_THAN, + countNumber: rule.maxImageAge !== undefined ? rule.maxImageAge.toDays() : rule.maxImageCount, + countUnit: rule.maxImageAge !== undefined ? 'days' : undefined, }, action: { type: 'expire' diff --git a/packages/@aws-cdk/aws-ecr/test/test.repository.ts b/packages/@aws-cdk/aws-ecr/test/test.repository.ts index 169b31a3aa4b7..b0bf0e7a39365 100644 --- a/packages/@aws-cdk/aws-ecr/test/test.repository.ts +++ b/packages/@aws-cdk/aws-ecr/test/test.repository.ts @@ -54,7 +54,7 @@ export = { // WHEN const repo = new ecr.Repository(stack, 'Repo'); repo.addLifecycleRule({ - maxImageAgeDays: 5, + maxImageAge: cdk.Duration.days(5), }); // THEN @@ -95,8 +95,8 @@ export = { const repo = new ecr.Repository(stack, 'Repo'); // WHEN - repo.addLifecycleRule({ tagStatus: ecr.TagStatus.Tagged, tagPrefixList: ['a'], maxImageCount: 5 }); - repo.addLifecycleRule({ rulePriority: 10, tagStatus: ecr.TagStatus.Tagged, tagPrefixList: ['b'], maxImageCount: 5 }); + repo.addLifecycleRule({ tagStatus: ecr.TagStatus.TAGGED, tagPrefixList: ['a'], maxImageCount: 5 }); + repo.addLifecycleRule({ rulePriority: 10, tagStatus: ecr.TagStatus.TAGGED, tagPrefixList: ['b'], maxImageCount: 5 }); // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { @@ -116,7 +116,7 @@ export = { // WHEN repo.addLifecycleRule({ maxImageCount: 5 }); - repo.addLifecycleRule({ tagStatus: ecr.TagStatus.Tagged, tagPrefixList: ['important'], maxImageCount: 999 }); + repo.addLifecycleRule({ tagStatus: ecr.TagStatus.TAGGED, tagPrefixList: ['important'], maxImageCount: 999 }); // THEN expect(stack).to(haveResource('AWS::ECR::Repository', { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/load-balanced-fargate-service.ts index 9343f00fcb237..c26cfa180b5f9 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/load-balanced-fargate-service.ts @@ -68,9 +68,9 @@ export interface LoadBalancedFargateServiceProps extends LoadBalancedServiceBase /** * Override value for the service name * - * @default - No value + * @default CloudFormation-generated name */ - readonly serviceName?: string; + readonly serviceName?: cdk.PhysicalName; } /** @@ -110,7 +110,7 @@ export class LoadBalancedFargateService extends LoadBalancedServiceBase { desiredCount: props.desiredCount || 1, taskDefinition, assignPublicIp, - serviceName: props.serviceName || undefined + serviceName: props.serviceName, }); this.service = service; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json index 5d7c2f56c5e2c..61916c2affd30 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.expected.json @@ -506,7 +506,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.ts index 080e00e1919ee..d17daca0799d9 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.scheduled-ecs-task.lit.ts @@ -27,7 +27,7 @@ class EventStack extends cdk.Stack { memoryLimitMiB: 512, cpu: 1, environment: { name: 'TRIGGER', value: 'CloudWatch Events' }, - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); /// !hide } diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts index c8337c1866a76..09a05dfe8e80f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.queue-processing-ecs-service.ts @@ -71,7 +71,9 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); - const queue = new sqs.Queue(stack, 'ecs-test-queue', { queueName: 'ecs-test-sqs-queue'}); + const queue = new sqs.Queue(stack, 'ecs-test-queue', { + queueName: cdk.PhysicalName.of('ecs-test-sqs-queue'), + }); // WHEN new ecsPatterns.QueueProcessingEc2Service(stack, 'Service', { @@ -134,4 +136,4 @@ export = { test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json index 509076c8949a5..7a24b26882683 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.asset-image.expected.json @@ -779,6 +779,7 @@ } } ], + "HealthCheckGracePeriodSeconds": 60, "NetworkConfiguration": { "AwsvpcConfiguration": { "AssignPublicIp": "DISABLED", @@ -1038,4 +1039,4 @@ "Description": "Artifact hash for asset \"aws-ecs-integ/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json index 3169c50366974..0fde02d4dfda5 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.expected.json @@ -615,6 +615,7 @@ }, "DesiredCount": 1, "LaunchType": "FARGATE", + "HealthCheckGracePeriodSeconds": 60, "LoadBalancers": [ { "ContainerName": "web", @@ -701,4 +702,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.ts index 89627269e9576..3043cfb86d900 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.executionrole.ts @@ -24,4 +24,4 @@ new ecsPatterns.LoadBalancedFargateService(stack, 'L3', { }) }); -app.run(); +app.synth(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json index eb3d65d3c3cde..1124545dc7763 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.l3.expected.json @@ -602,6 +602,7 @@ }, "DesiredCount": 1, "LaunchType": "FARGATE", + "HealthCheckGracePeriodSeconds": 60, "LoadBalancers": [ { "ContainerName": "web", diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts index 327103c71b0e5..3224d06c61fab 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.load-balanced-fargate-service.ts @@ -155,7 +155,7 @@ export = { cluster, loadBalancerType: ecsPatterns.LoadBalancerType.Network, image: ecs.ContainerImage.fromRegistry("/aws/aws-example-app"), - serviceName: 'bob' + serviceName: cdk.PhysicalName.of('bob'), }); // THEN const serviceTaskDefinition = SynthUtils.synthesize(stack).template.Resources.Service9571FDD8; @@ -181,4 +181,4 @@ export = { test.equal(serviceTaskDefinition.Properties.ServiceName, undefined); test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts index dbc80913ac6fd..6d79f0003906c 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/test.queue-processing-fargate-service.ts @@ -69,7 +69,9 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); - const queue = new sqs.Queue(stack, 'fargate-test-queue', { queueName: 'fargate-test-sqs-queue'}); + const queue = new sqs.Queue(stack, 'fargate-test-queue', { + queueName: cdk.PhysicalName.of('fargate-test-sqs-queue'), + }); // WHEN new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index a7f22e24836f5..2c0a084597f34 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -4,8 +4,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); import cloudmap = require('@aws-cdk/aws-servicediscovery'); -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/cdk'; -import cdk = require('@aws-cdk/cdk'); +import { Construct, Duration, Fn, IResolvable, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; @@ -41,7 +40,7 @@ export interface BaseServiceProps { * * @default - CloudFormation-generated name. */ - readonly serviceName?: string; + readonly serviceName?: PhysicalName; /** * The maximum number of tasks, specified as a percentage of the Amazon ECS @@ -64,16 +63,16 @@ export interface BaseServiceProps { /** * Time after startup to ignore unhealthy load balancer checks. * - * @default ??? FIXME + * @default - defaults to 60 seconds if at least one load balancer is in-use and it is not already set */ - readonly healthCheckGracePeriodSeconds?: number; + readonly healthCheckGracePeriod?: Duration; /** * Options for enabling AWS Cloud Map service discovery for the service * * @default - AWS Cloud Map service discovery is not enabled. */ - readonly serviceDiscoveryOptions?: ServiceDiscoveryOptions; + readonly cloudMapOptions?: CloudMapOptions; /** * Whether the new long ARN format has been enabled on ECS services. @@ -115,17 +114,16 @@ export abstract class BaseService extends Resource public readonly serviceName: string; /** - * Name of this service's cluster + * Task definition this service is associated with */ - public readonly clusterName: string; + public readonly taskDefinition: TaskDefinition; /** - * Task definition this service is associated with + * The cluster this service is scheduled on */ - public readonly taskDefinition: TaskDefinition; + public readonly cluster: ICluster; protected cloudmapService?: cloudmap.Service; - protected cluster: ICluster; protected loadBalancers = new Array(); protected networkConfiguration?: CfnService.NetworkConfigurationProperty; protected serviceRegistries = new Array(); @@ -133,45 +131,55 @@ export abstract class BaseService extends Resource private readonly resource: CfnService; private scalableTaskCount?: ScalableTaskCount; - constructor(scope: cdk.Construct, + constructor(scope: Construct, id: string, props: BaseServiceProps, additionalProps: any, - clusterName: string, taskDefinition: TaskDefinition) { - super(scope, id); + super(scope, id, { + physicalName: props.serviceName, + }); this.taskDefinition = taskDefinition; this.resource = new CfnService(this, "Service", { desiredCount: props.desiredCount, - serviceName: props.serviceName, + serviceName: this.physicalName, loadBalancers: Lazy.anyValue({ produce: () => this.loadBalancers }), deploymentConfiguration: { maximumPercent: props.maximumPercent || 200, minimumHealthyPercent: props.minimumHealthyPercent === undefined ? 50 : props.minimumHealthyPercent }, - healthCheckGracePeriodSeconds: props.healthCheckGracePeriodSeconds, + healthCheckGracePeriodSeconds: this.evaluateHealthGracePeriod(props.healthCheckGracePeriod), /* role: never specified, supplanted by Service Linked Role */ networkConfiguration: Lazy.anyValue({ produce: () => this.networkConfiguration }), serviceRegistries: Lazy.anyValue({ produce: () => this.serviceRegistries }), ...additionalProps }); - this.serviceArn = this.resource.refAsString; - // This is a workaround for CFN bug that returns the cluster name instead of the service name when long ARN formats // are enabled for the principal in a given region. const longArnEnabled = props.longArnEnabled !== undefined ? props.longArnEnabled : false; - this.serviceName = longArnEnabled - ? cdk.Fn.select(2, cdk.Fn.split('/', this.serviceArn)) + const serviceName = longArnEnabled + ? Fn.select(2, Fn.split('/', this.resource.ref)) : this.resource.attrName; - this.clusterName = clusterName; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: this.resource.ref, + name: serviceName, + arnComponents: { + service: 'ecs', + resource: 'service', + resourceName: `${props.cluster.clusterName}/${this.physicalName}`, + }, + }); + this.serviceArn = resourceIdentifiers.arn; + this.serviceName = resourceIdentifiers.name; + this.cluster = props.cluster; - if (props.serviceDiscoveryOptions) { - this.enableServiceDiscovery(props.serviceDiscoveryOptions); + if (props.cloudMapOptions) { + this.enableCloudMap(props.cloudMapOptions); } } @@ -187,7 +195,7 @@ export abstract class BaseService extends Resource // Open up security groups. For dynamic port mapping, we won't know the port range // in advance so we need to open up all ports. const port = this.taskDefinition.defaultContainer!.ingressPort; - const portRange = port === 0 ? EPHEMERAL_PORT_RANGE : new ec2.TcpPort(port); + const portRange = port === 0 ? EPHEMERAL_PORT_RANGE : ec2.Port.tcp(port); targetGroup.registerConnectable(this, portRange); return ret; @@ -212,8 +220,8 @@ export abstract class BaseService extends Resource } return this.scalableTaskCount = new ScalableTaskCount(this, 'TaskCount', { - serviceNamespace: appscaling.ServiceNamespace.Ecs, - resourceId: `service/${this.clusterName}/${this.serviceName}`, + serviceNamespace: appscaling.ServiceNamespace.ECS, + resourceId: `service/${this.cluster.clusterName}/${this.serviceName}`, dimension: 'ecs:service:DesiredCount', role: this.makeAutoScalingRole(), ...props @@ -227,7 +235,7 @@ export abstract class BaseService extends Resource return new cloudwatch.Metric({ namespace: 'AWS/ECS', metricName, - dimensions: { ClusterName: this.clusterName, ServiceName: this.serviceName }, + dimensions: { ClusterName: this.cluster.clusterName, ServiceName: this.serviceName }, ...props }); } @@ -256,7 +264,7 @@ export abstract class BaseService extends Resource // tslint:disable-next-line:max-line-length protected configureAwsVpcNetworking(vpc: ec2.IVpc, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (vpcSubnets === undefined) { - vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); @@ -325,7 +333,7 @@ export abstract class BaseService extends Resource /** * Enable CloudMap service discovery for the service */ - private enableServiceDiscovery(options: ServiceDiscoveryOptions): cloudmap.Service { + private enableCloudMap(options: CloudMapOptions): cloudmap.Service { const sdNamespace = this.cluster.defaultNamespace; if (sdNamespace === undefined) { throw new Error("Cannot enable service discovery if a Cloudmap Namespace has not been created in the cluster."); @@ -381,17 +389,29 @@ export abstract class BaseService extends Resource return cloudmapService; } + + /** + * Return the default grace period when load balancers are configured and + * healthCheckGracePeriod is not already set + */ + private evaluateHealthGracePeriod(providedHealthCheckGracePeriod?: Duration): IResolvable { + return Lazy.anyValue({ + produce: () => providedHealthCheckGracePeriod !== undefined ? providedHealthCheckGracePeriod.toSeconds() : + this.loadBalancers.length > 0 ? 60 : + undefined + }); + } } /** * The port range to open up for dynamic port mapping */ -const EPHEMERAL_PORT_RANGE = new ec2.TcpPortRange(32768, 65535); +const EPHEMERAL_PORT_RANGE = ec2.Port.tcpRange(32768, 65535); /** - * Options for enabling service discovery on an ECS service + * Options for enabling CloudMap on an ECS service */ -export interface ServiceDiscoveryOptions { +export interface CloudMapOptions { /** * Name of the cloudmap service to attach to the ECS Service * @@ -411,7 +431,7 @@ export interface ServiceDiscoveryOptions { * * @default 60 */ - readonly dnsTtlSec?: number; + readonly dnsTtl?: Duration; /** * The number of 30-second intervals that you want Cloud Map to wait after receiving an diff --git a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts index 1b495a57b00ae..b9164b3b964e4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts @@ -36,12 +36,12 @@ export class ScalableTaskCount extends appscaling.BaseScalableAttribute { */ public scaleOnCpuUtilization(id: string, props: CpuUtilizationScalingProps) { return super.doScaleToTrackMetric(id, { - predefinedMetric: appscaling.PredefinedMetric.ECSServiceAverageCPUUtilization, + predefinedMetric: appscaling.PredefinedMetric.ECS_SERVICE_AVERAGE_CPU_UTILIZATION, policyName: props.policyName, disableScaleIn: props.disableScaleIn, targetValue: props.targetUtilizationPercent, - scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown }); } @@ -50,12 +50,12 @@ export class ScalableTaskCount extends appscaling.BaseScalableAttribute { */ public scaleOnMemoryUtilization(id: string, props: MemoryUtilizationScalingProps) { return super.doScaleToTrackMetric(id, { - predefinedMetric: appscaling.PredefinedMetric.ECSServiceAverageMemoryUtilization, + predefinedMetric: appscaling.PredefinedMetric.ECS_SERVICE_AVERAGE_MEMORY_UTILIZATION, targetValue: props.targetUtilizationPercent, policyName: props.policyName, disableScaleIn: props.disableScaleIn, - scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown }); } @@ -67,13 +67,13 @@ export class ScalableTaskCount extends appscaling.BaseScalableAttribute { '/' + props.targetGroup.targetGroupFullName; return super.doScaleToTrackMetric(id, { - predefinedMetric: appscaling.PredefinedMetric.ALBRequestCountPerTarget, + predefinedMetric: appscaling.PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET, resourceLabel, targetValue: props.requestsPerTarget, policyName: props.policyName, disableScaleIn: props.disableScaleIn, - scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown }); } @@ -86,8 +86,8 @@ export class ScalableTaskCount extends appscaling.BaseScalableAttribute { targetValue: props.targetValue, policyName: props.policyName, disableScaleIn: props.disableScaleIn, - scaleInCooldownSec: props.scaleInCooldownSec, - scaleOutCooldownSec: props.scaleOutCooldownSec, + scaleInCooldown: props.scaleInCooldown, + scaleOutCooldown: props.scaleOutCooldown, }); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 0b99b08e229de..d04b25c72fb3f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -283,7 +283,7 @@ export class TaskDefinition extends TaskDefinitionBase { props.placementConstraints.forEach(pc => this.addPlacementConstraint(pc)); } - this.taskDefinitionArn = taskDef.refAsString; + this.taskDefinitionArn = taskDef.ref; } public get executionRole(): iam.IRole | undefined { diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index d3981b4bc9314..ae71390201604 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -4,7 +4,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import cloudmap = require('@aws-cdk/aws-servicediscovery'); import ssm = require('@aws-cdk/aws-ssm'); -import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { InstanceDrainHook } from './drain-hook/instance-drain-hook'; import { CfnCluster } from './ecs.generated'; @@ -17,7 +17,7 @@ export interface ClusterProps { * * @default CloudFormation-generated name */ - readonly clusterName?: string; + readonly clusterName?: PhysicalName; /** * The VPC where your ECS instances will be running or your ENIs will be deployed @@ -67,13 +67,27 @@ export class Cluster extends Resource implements ICluster { private _hasEc2Capacity: boolean = false; constructor(scope: Construct, id: string, props: ClusterProps) { - super(scope, id); + super(scope, id, { + physicalName: props.clusterName, + }); - const cluster = new CfnCluster(this, 'Resource', {clusterName: props.clusterName}); + const cluster = new CfnCluster(this, 'Resource', { + clusterName: this.physicalName, + }); + + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: cluster.attrArn, + name: cluster.ref, + arnComponents: { + service: 'ecs', + resource: 'cluster', + resourceName: this.physicalName, + }, + }); + this.clusterArn = resourceIdentifiers.arn; + this.clusterName = resourceIdentifiers.name; this.vpc = props.vpc; - this.clusterArn = cluster.attrArn; - this.clusterName = cluster.refAsString; } /** @@ -86,11 +100,11 @@ export class Cluster extends Resource implements ICluster { throw new Error("Can only add default namespace once."); } - const namespaceType = options.type === undefined || options.type === NamespaceType.PrivateDns - ? cloudmap.NamespaceType.DnsPrivate - : cloudmap.NamespaceType.DnsPublic; + const namespaceType = options.type === undefined || options.type === NamespaceType.PRIVATE_DNS + ? cloudmap.NamespaceType.DNS_PRIVATE + : cloudmap.NamespaceType.DNS_PUBLIC; - const sdNamespace = namespaceType === cloudmap.NamespaceType.DnsPrivate ? + const sdNamespace = namespaceType === cloudmap.NamespaceType.DNS_PRIVATE ? new cloudmap.PrivateDnsNamespace(this, 'DefaultServiceDiscoveryNamespace', { name: options.name, vpc: this.vpc @@ -121,7 +135,7 @@ export class Cluster extends Resource implements ICluster { ...options, vpc: this.vpc, machineImage: options.machineImage || new EcsOptimizedAmi(), - updateType: options.updateType || autoscaling.UpdateType.ReplacingUpdate, + updateType: options.updateType || autoscaling.UpdateType.REPLACING_UPDATE, instanceType: options.instanceType, }); @@ -143,7 +157,7 @@ export class Cluster extends Resource implements ICluster { // Tie instances to cluster autoScalingGroup.addUserData(`echo ECS_CLUSTER=${this.clusterName} >> /etc/ecs/ecs.config`); - if (!options.containersAccessInstanceRole) { + if (!options.canContainersAccessInstanceRole) { // Deny containers access to instance metadata service // Source: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html autoScalingGroup.addUserData('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP'); @@ -171,11 +185,11 @@ export class Cluster extends Resource implements ICluster { })); // 0 disables, otherwise forward to underlying implementation which picks the sane default - if (options.taskDrainTimeSeconds !== 0) { + if (!options.taskDrainTime || options.taskDrainTime.toSeconds() !== 0) { new InstanceDrainHook(autoScalingGroup, 'DrainECSHook', { autoScalingGroup, cluster: this, - drainTimeSec: options.taskDrainTimeSeconds + drainTime: options.taskDrainTime }); } } @@ -237,43 +251,46 @@ export interface EcsOptimizedAmiProps { /** * Construct a Linux machine image from the latest ECS Optimized AMI published in SSM */ -export class EcsOptimizedAmi implements ec2.IMachineImageSource { +export class EcsOptimizedAmi implements ec2.IMachineImage { private readonly generation: ec2.AmazonLinuxGeneration; private readonly hwType: AmiHardwareType; private readonly amiParameterName: string; constructor(props?: EcsOptimizedAmiProps) { - this.hwType = (props && props.hardwareType) || AmiHardwareType.Standard; + this.hwType = (props && props.hardwareType) || AmiHardwareType.STANDARD; if (props && props.generation) { // generation defined in the props object - if (props.generation === ec2.AmazonLinuxGeneration.AmazonLinux && this.hwType !== AmiHardwareType.Standard) { + if (props.generation === ec2.AmazonLinuxGeneration.AMAZON_LINUX && this.hwType !== AmiHardwareType.STANDARD) { throw new Error(`Amazon Linux does not support special hardware type. Use Amazon Linux 2 instead`); } else { this.generation = props.generation; } } else { // generation not defined in props object - if (this.hwType === AmiHardwareType.Standard) { // default to Amazon Linux v1 if no HW is standard - this.generation = ec2.AmazonLinuxGeneration.AmazonLinux; + if (this.hwType === AmiHardwareType.STANDARD) { // default to Amazon Linux v1 if no HW is standard + this.generation = ec2.AmazonLinuxGeneration.AMAZON_LINUX; } else { // default to Amazon Linux v2 if special HW - this.generation = ec2.AmazonLinuxGeneration.AmazonLinux2; + this.generation = ec2.AmazonLinuxGeneration.AMAZON_LINUX_2; } } // set the SSM parameter name this.amiParameterName = "/aws/service/ecs/optimized-ami/" - + ( this.generation === ec2.AmazonLinuxGeneration.AmazonLinux ? "amazon-linux/" : "" ) - + ( this.generation === ec2.AmazonLinuxGeneration.AmazonLinux2 ? "amazon-linux-2/" : "" ) - + ( this.hwType === AmiHardwareType.Gpu ? "gpu/" : "" ) - + ( this.hwType === AmiHardwareType.Arm ? "arm64/" : "" ) + + ( this.generation === ec2.AmazonLinuxGeneration.AMAZON_LINUX ? "amazon-linux/" : "" ) + + ( this.generation === ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 ? "amazon-linux-2/" : "" ) + + ( this.hwType === AmiHardwareType.GPU ? "gpu/" : "" ) + + ( this.hwType === AmiHardwareType.ARM ? "arm64/" : "" ) + "recommended/image_id"; } /** * Return the correct image */ - public getImage(scope: Construct): ec2.MachineImage { + public getImage(scope: Construct): ec2.MachineImageConfig { const ami = ssm.StringParameter.valueForStringParameter(scope, this.amiParameterName); - return new ec2.MachineImage(ami, new ec2.LinuxOS()); + return { + imageId: ami, + osType: ec2.OperatingSystemType.LINUX + }; } } @@ -423,7 +440,7 @@ export interface AddAutoScalingGroupCapacityOptions { * * @default false */ - readonly containersAccessInstanceRole?: boolean; + readonly canContainersAccessInstanceRole?: boolean; /** * Give tasks this many seconds to complete when instances are being scaled in. @@ -434,9 +451,9 @@ export interface AddAutoScalingGroupCapacityOptions { * * Set to 0 to disable task draining. * - * @default 300 + * @default Duration.minutes(5) */ - readonly taskDrainTimeSeconds?: number; + readonly taskDrainTime?: Duration; } /** @@ -453,7 +470,7 @@ export interface AddCapacityOptions extends AddAutoScalingGroupCapacityOptions, * * @default - Amazon Linux 1 */ - readonly machineImage?: ec2.IMachineImageSource; + readonly machineImage?: ec2.IMachineImage; } export interface NamespaceOptions { @@ -484,12 +501,12 @@ export enum NamespaceType { /** * Create a private DNS namespace */ - PrivateDns = 'PrivateDns', + PRIVATE_DNS = 'PrivateDns', /** * Create a public DNS namespace */ - PublicDns = 'PublicDns', + PUBLIC_DNS = 'PublicDns', } /** @@ -500,15 +517,15 @@ export enum AmiHardwareType { /** * Create a standard AMI */ - Standard = 'Standard', + STANDARD = 'Standard', /** * Create a GPU optimized AMI */ - Gpu = 'GPU', + GPU = 'GPU', /** * Create a ARM64 optimized AMI */ - Arm = 'ARM64', + ARM = 'ARM64', } diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 59f8e08e0c660..2342a78fc5fab 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -436,9 +436,9 @@ export interface HealthCheck { * * You may specify between 5 and 300 seconds. * - * @default 30 + * @default Duration.seconds(30) */ - readonly intervalSeconds?: number; + readonly interval?: cdk.Duration; /** * Number of times to retry a failed health check before the container is considered unhealthy. @@ -456,16 +456,16 @@ export interface HealthCheck { * * @default No start period */ - readonly startPeriod?: number; + readonly startPeriod?: cdk.Duration; /** * The time period in seconds to wait for a health check to succeed before it is considered a failure. * * You may specify between 2 and 60 seconds. * - * @default 5 + * @default Duration.seconds(5) */ - readonly timeout?: number; + readonly timeout?: cdk.Duration; } function renderKV(env: { [key: string]: string }, keyName: string, valueName: string): any { @@ -479,10 +479,10 @@ function renderKV(env: { [key: string]: string }, keyName: string, valueName: st function renderHealthCheck(hc: HealthCheck): CfnTaskDefinition.HealthCheckProperty { return { command: getHealthCheckCommand(hc), - interval: hc.intervalSeconds !== undefined ? hc.intervalSeconds : 30, + interval: hc.interval != null ? hc.interval.toSeconds() : 30, retries: hc.retries !== undefined ? hc.retries : 3, - startPeriod: hc.startPeriod, - timeout: hc.timeout !== undefined ? hc.timeout : 5, + startPeriod: hc.startPeriod && hc.startPeriod.toSeconds(), + timeout: hc.timeout !== undefined ? hc.timeout.toSeconds() : 5, }; } @@ -534,21 +534,21 @@ export interface Ulimit { * Type of resource to set a limit on */ export enum UlimitName { - Core = "core", - Cpu = "cpu", - Data = "data", - Fsize = "fsize", - Locks = "locks", - Memlock = "memlock", - Msgqueue = "msgqueue", - Nice = "nice", - Nofile = "nofile", - Nproc = "nproc", - Rss = "rss", - Rtprio = "rtprio", - Rttime = "rttime", - Sigpending = "sigpending", - Stack = "stack" + CORE = "core", + CPU = "cpu", + DATA = "data", + FSIZE = "fsize", + LOCKS = "locks", + MEMLOCK = "memlock", + MSGQUEUE = "msgqueue", + NICE = "nice", + NOFILE = "nofile", + NPROC = "nproc", + RSS = "rss", + RTPRIO = "rtprio", + RTTIME = "rttime", + SIGPENDING = "sigpending", + STACK = "stack" } function renderUlimit(ulimit: Ulimit): CfnTaskDefinition.UlimitProperty { @@ -594,19 +594,19 @@ export enum Protocol { /** * TCP */ - Tcp = "tcp", + TCP = "tcp", /** * UDP */ - Udp = "udp", + UDP = "udp", } function renderPortMapping(pm: PortMapping): CfnTaskDefinition.PortMappingProperty { return { containerPort: pm.containerPort, hostPort: pm.hostPort, - protocol: pm.protocol || Protocol.Tcp, + protocol: pm.protocol || Protocol.TCP, }; } diff --git a/packages/@aws-cdk/aws-ecs/lib/drain-hook/instance-drain-hook.ts b/packages/@aws-cdk/aws-ecs/lib/drain-hook/instance-drain-hook.ts index a8fc401e198ea..18d036c22a858 100644 --- a/packages/@aws-cdk/aws-ecs/lib/drain-hook/instance-drain-hook.ts +++ b/packages/@aws-cdk/aws-ecs/lib/drain-hook/instance-drain-hook.ts @@ -28,11 +28,11 @@ export interface InstanceDrainHookProps { /** * How many seconds to give tasks to drain before the instance is terminated anyway * - * Must be between 0 and 900. + * Must be between 0 and 15 minutes. * - * @default 900 + * @default Duration.minutes(15) */ - drainTimeSec?: number; + drainTime?: cdk.Duration; } /** @@ -42,11 +42,7 @@ export class InstanceDrainHook extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: InstanceDrainHookProps) { super(scope, id); - const drainTimeSeconds = props.drainTimeSec !== undefined ? props.drainTimeSec : 300; - - if (drainTimeSeconds < 0 || drainTimeSeconds > 900) { - throw new Error(`Drain time must be between 0 and 900 seconds, got: ${drainTimeSeconds}`); - } + const drainTime = props.drainTime || cdk.Duration.minutes(5); // Invoke Lambda via SNS Topic const fn = new lambda.Function(this, 'Function', { @@ -55,7 +51,7 @@ export class InstanceDrainHook extends cdk.Construct { runtime: lambda.Runtime.Python36, // Timeout: some extra margin for additional API calls made by the Lambda, // up to a maximum of 15 minutes. - timeout: Math.min(drainTimeSeconds + 10, 900), + timeout: cdk.Duration.seconds(Math.min(drainTime.toSeconds() + 10, 900)), environment: { CLUSTER: props.cluster.clusterName } @@ -63,10 +59,10 @@ export class InstanceDrainHook extends cdk.Construct { // Hook everything up: ASG -> Topic, Topic -> Lambda props.autoScalingGroup.addLifecycleHook('DrainHook', { - lifecycleTransition: autoscaling.LifecycleTransition.InstanceTerminating, - defaultResult: autoscaling.DefaultResult.Continue, + lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_TERMINATING, + defaultResult: autoscaling.DefaultResult.CONTINUE, notificationTarget: new hooks.FunctionHook(fn), - heartbeatTimeoutSec: drainTimeSeconds, + heartbeatTimeout: drainTime, }); // Describe actions cannot be restricted and restrict the CompleteLifecycleAction to the ASG arn diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index dccf0cd573e4a..98e5da7eb320f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -4,7 +4,7 @@ import { Construct, Lazy, Resource } from '@aws-cdk/cdk'; import { BaseService, BaseServiceProps, IService } from '../base/base-service'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { CfnService } from '../ecs.generated'; -import { BinPackResource, PlacementConstraint, PlacementStrategy } from '../placement'; +import { PlacementConstraint, PlacementStrategy } from '../placement'; /** * Properties to define an ECS service @@ -85,11 +85,6 @@ export class Ec2Service extends BaseService implements IEc2Service, elb.ILoadBal return new Import(scope, id); } - /** - * Name of the cluster - */ - public readonly clusterName: string; - private readonly constraints: CfnService.PlacementConstraintProperty[]; private readonly strategies: CfnService.PlacementStrategyProperty[]; private readonly daemon: boolean; @@ -125,9 +120,8 @@ export class Ec2Service extends BaseService implements IEc2Service, elb.ILoadBal placementConstraints: Lazy.anyValue({ produce: () => this.constraints }, { omitEmptyArray: true }), placementStrategies: Lazy.anyValue({ produce: () => this.strategies }, { omitEmptyArray: true }), schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', - }, props.cluster.clusterName, props.taskDefinition); + }, props.taskDefinition); - this.clusterName = props.cluster.clusterName; this.constraints = []; this.strategies = []; this.daemon = props.daemon || false; @@ -148,57 +142,6 @@ export class Ec2Service extends BaseService implements IEc2Service, elb.ILoadBal } } - /** - * Place tasks only on instances matching the given query expression - * - * You can specify multiple expressions in one call. The tasks will only - * be placed on instances matching all expressions. - * - * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html - * @deprecated Use addPlacementConstraints() instead. - */ - public placeOnMemberOf(...expressions: string[]) { - this.addPlacementConstraints(PlacementConstraint.memberOf(...expressions)); - } - - /** - * Try to place tasks spread across instance attributes. - * - * You can use one of the built-in attributes found on `BuiltInAttributes` - * or supply your own custom instance attributes. If more than one attribute - * is supplied, spreading is done in order. - * - * @default attributes instanceId - * @deprecated Use addPlacementStrategies() instead. - */ - public placeSpreadAcross(...fields: string[]) { - if (fields.length === 0) { - this.addPlacementStrategies(PlacementStrategy.spreadAcrossInstances()); - } else { - this.addPlacementStrategies(PlacementStrategy.spreadAcross(...fields)); - } - } - - /** - * Try to place tasks on instances with the least amount of indicated resource available - * - * This ensures the total consumption of this resource is lowest. - * - * @deprecated Use addPlacementStrategies() instead. - */ - public placePackedBy(resource: BinPackResource) { - this.addPlacementStrategies(PlacementStrategy.packedBy(resource)); - } - - /** - * Place tasks randomly across the available instances. - * - * @deprecated Use addPlacementStrategies() instead. - */ - public placeRandomly() { - this.addPlacementStrategies(PlacementStrategy.randomly()); - } - /** * Add one or more placement strategies */ diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 8a7c03d0937cf..bd92b644e0f0b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -78,7 +78,7 @@ export class FargateService extends BaseService implements IFargateService { taskDefinition: props.taskDefinition.taskDefinitionArn, launchType: 'FARGATE', platformVersion: props.platformVersion, - }, props.cluster.clusterName, props.taskDefinition); + }, props.taskDefinition); this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcSubnets, props.securityGroup); diff --git a/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts b/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts index 4630cf7a18420..715ec3f66d0d4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts +++ b/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts @@ -176,44 +176,44 @@ function renderTmpfs(tmpfs: Tmpfs): CfnTaskDefinition.TmpfsProperty { * A Linux capability */ export enum Capability { - All = "ALL", - AuditControl = "AUDIT_CONTROL", - AuditWrite = "AUDIT_WRITE", - BlockSuspend = "BLOCK_SUSPEND", - Chown = "CHOWN", - DacOverride = "DAC_OVERRIDE", - DacReadSearch = "DAC_READ_SEARCH", - Fowner = "FOWNER", - Fsetid = "FSETID", - IpcLock = "IPC_LOCK", - IpcOwner = "IPC_OWNER", - Kill = "KILL", - Lease = "LEASE", - LinuxImmutable = "LINUX_IMMUTABLE", - MacAdmin = "MAC_ADMIN", - MacOverride = "MAC_OVERRIDE", - Mknod = "MKNOD", - NetAdmin = "NET_ADMIN", - NetBindService = "NET_BIND_SERVICE", - NetBroadcast = "NET_BROADCAST", - NetRaw = "NET_RAW", - Setfcap = "SETFCAP", - Setgid = "SETGID", - Setpcap = "SETPCAP", - Setuid = "SETUID", - SysAdmin = "SYS_ADMIN", - SysBoot = "SYS_BOOT", - SysChroot = "SYS_CHROOT", - SysModule = "SYS_MODULE", - SysNice = "SYS_NICE", - SysPacct = "SYS_PACCT", - SysPtrace = "SYS_PTRACE", - SysRawio = "SYS_RAWIO", - SysResource = "SYS_RESOURCE", - SysTime = "SYS_TIME", - SysTtyConfig = "SYS_TTY_CONFIG", - Syslog = "SYSLOG", - WakeAlarm = "WAKE_ALARM" + ALL = "ALL", + AUDIT_CONTROL = "AUDIT_CONTROL", + AUDIT_WRITE = "AUDIT_WRITE", + BLOCK_SUSPEND = "BLOCK_SUSPEND", + CHOWN = "CHOWN", + DAC_OVERRIDE = "DAC_OVERRIDE", + DAC_READ_SEARCH = "DAC_READ_SEARCH", + FOWNER = "FOWNER", + FSETID = "FSETID", + IPC_LOCK = "IPC_LOCK", + IPC_OWNER = "IPC_OWNER", + KILL = "KILL", + LEASE = "LEASE", + LINUX_IMMUTABLE = "LINUX_IMMUTABLE", + MAC_ADMIN = "MAC_ADMIN", + MAC_OVERRIDE = "MAC_OVERRIDE", + MKNOD = "MKNOD", + NET_ADMIN = "NET_ADMIN", + NET_BIND_SERVICE = "NET_BIND_SERVICE", + NET_BROADCAST = "NET_BROADCAST", + NET_RAW = "NET_RAW", + SETFCAP = "SETFCAP", + SETGID = "SETGID", + SETPCAP = "SETPCAP", + SETUID = "SETUID", + SYS_ADMIN = "SYS_ADMIN", + SYS_BOOT = "SYS_BOOT", + SYS_CHROOT = "SYS_CHROOT", + SYS_MODULE = "SYS_MODULE", + SYS_NICE = "SYS_NICE", + SYS_PACCT = "SYS_PACCT", + SYS_PTRACE = "SYS_PTRACE", + SYS_RAWIO = "SYS_RAWIO", + SYS_RESOURCE = "SYS_RESOURCE", + SYS_TIME = "SYS_TIME", + SYS_TTY_CONFIG = "SYS_TTY_CONFIG", + SYSLOG = "SYSLOG", + WAKE_ALARM = "WAKE_ALARM" } /** @@ -223,60 +223,60 @@ export enum DevicePermission { /** * Read */ - Read = "read", + READ = "read", /** * Write */ - Write = "write", + WRITE = "write", /** * Make a node */ - Mknod = "mknod", + MKNOD = "mknod", } /** * Options for a tmpfs mount */ export enum TmpfsMountOption { - Defaults = "defaults", - Ro = "ro", - Rw = "rw", - Suid = "suid", - Nosuid = "nosuid", - Dev = "dev", - Nodev = "nodev", - Exec = "exec", - Noexec = "noexec", - Sync = "sync", - Async = "async", - Dirsync = "dirsync", - Remount = "remount", - Mand = "mand", - Nomand = "nomand", - Atime = "atime", - Noatime = "noatime", - Diratime = "diratime", - Nodiratime = "nodiratime", - Bind = "bind", - Rbind = "rbind", - Unbindable = "unbindable", - Runbindable = "runbindable", - Private = "private", - Rprivate = "rprivate", - Shared = "shared", - Rshared = "rshared", - Slave = "slave", - Rslave = "rslave", - Relatime = "relatime", - Norelatime = "norelatime", - Strictatime = "strictatime", - Nostrictatime = "nostrictatime", - Mode = "mode", - Uid = "uid", - Gid = "gid", - NrInodes = "nr_inodes", - NrBlocks = "nr_blocks", - Mpol = "mpol" + DEFAULTS = "defaults", + RO = "ro", + RW = "rw", + SUID = "suid", + NOSUID = "nosuid", + DEV = "dev", + NODEV = "nodev", + EXEC = "exec", + NOEXEC = "noexec", + SYNC = "sync", + ASYNC = "async", + DIRSYNC = "dirsync", + REMOUNT = "remount", + MAND = "mand", + NOMAND = "nomand", + ATIME = "atime", + NOATIME = "noatime", + DIRATIME = "diratime", + NODIRATIME = "nodiratime", + BIND = "bind", + RBIND = "rbind", + UNBINDABLE = "unbindable", + RUNBINDABLE = "runbindable", + PRIVATE = "private", + RPRIVATE = "rprivate", + SHARED = "shared", + RSHARED = "rshared", + SLAVE = "slave", + RSLAVE = "rslave", + RELATIME = "relatime", + NORELATIME = "norelatime", + STRICTATIME = "strictatime", + NOSTRICTATIME = "nostrictatime", + MODE = "mode", + UID = "uid", + GID = "gid", + NR_INODES = "nr_inodes", + NR_BLOCKS = "nr_blocks", + MPOL = "mpol" } diff --git a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts index b21e77d74ccc0..2c752ba0ad1ea 100644 --- a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts @@ -34,7 +34,7 @@ export interface AwsLogDriverProps { * * @default - Logs never expire. */ - readonly logRetentionDays?: logs.RetentionDays; + readonly logRetention?: logs.RetentionDays; /** * This option defines a multiline start pattern in Python strftime format. @@ -73,12 +73,12 @@ export class AwsLogDriver extends LogDriver { constructor(scope: cdk.Construct, id: string, private readonly props: AwsLogDriverProps) { super(scope, id); - if (props.logGroup && props.logRetentionDays) { + if (props.logGroup && props.logRetention) { throw new Error('Cannot specify both `logGroup` and `logRetentionDays`.'); } this.logGroup = props.logGroup || new logs.LogGroup(this, 'LogGroup', { - retentionDays: props.logRetentionDays || Infinity, + retention: props.logRetention || Infinity, }); } diff --git a/packages/@aws-cdk/aws-ecs/lib/placement.ts b/packages/@aws-cdk/aws-ecs/lib/placement.ts index cacc8530de8fc..05477b89e5fba 100644 --- a/packages/@aws-cdk/aws-ecs/lib/placement.ts +++ b/packages/@aws-cdk/aws-ecs/lib/placement.ts @@ -8,12 +8,12 @@ export enum BinPackResource { /** * Fill up hosts' CPU allocations first */ - Cpu = 'cpu', + CPU = 'cpu', /** * Fill up hosts' memory allocations first */ - Memory = 'memory', + MEMORY = 'memory', } /** @@ -51,7 +51,7 @@ export class PlacementStrategy { * This ensures the total consumption of CPU is lowest */ public static packedByCpu() { - return PlacementStrategy.packedBy(BinPackResource.Cpu); + return PlacementStrategy.packedBy(BinPackResource.CPU); } /** @@ -60,7 +60,7 @@ export class PlacementStrategy { * This ensures the total consumption of memory is lowest */ public static packedByMemory() { - return PlacementStrategy.packedBy(BinPackResource.Memory); + return PlacementStrategy.packedBy(BinPackResource.MEMORY); } /** diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 6d3f9c61abaab..cb33a4f76ab96 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -125,5 +125,12 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-ecs.TaskDefinitionProps", + "props-physical-name:@aws-cdk/aws-ecs.Ec2TaskDefinitionProps", + "props-physical-name:@aws-cdk/aws-ecs.FargateTaskDefinitionProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index e3b29d4b8aae2..2c20119e93204 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -671,7 +671,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -852,6 +852,7 @@ "MinimumHealthyPercent": 50 }, "DesiredCount": 1, + "HealthCheckGracePeriodSeconds": 60, "LaunchType": "EC2", "LoadBalancers": [ { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts index c4dbf378c848f..993db77c86c9d 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts @@ -25,7 +25,7 @@ const container = taskDefinition.addContainer('web', { container.addPortMappings({ containerPort: 80, - protocol: ecs.Protocol.Tcp + protocol: ecs.Protocol.TCP }); const service = new ecs.Ec2Service(stack, "Service", { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index 64cd3a75c59a0..03344947e847b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -692,7 +692,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -874,6 +874,7 @@ "MinimumHealthyPercent": 50 }, "DesiredCount": 1, + "HealthCheckGracePeriodSeconds": 60, "LaunchType": "EC2", "LoadBalancers": [ { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts index c09cf9f14f1ee..7ee82b239b07d 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.ts @@ -27,7 +27,7 @@ const container = taskDefinition.addContainer('web', { container.addPortMappings({ containerPort: 80, hostPort: 8080, - protocol: ecs.Protocol.Tcp + protocol: ecs.Protocol.TCP }); const service = new ecs.Ec2Service(stack, "Service", { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json index 0793f9813976a..60393cefcb965 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json @@ -671,7 +671,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -946,4 +946,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts index 410e72a4d2dfa..49628588dd28d 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts @@ -33,13 +33,13 @@ const frontend = frontendTD.addContainer('frontend', { frontend.addPortMappings({ containerPort: 80, hostPort: 80, - protocol: ecs.Protocol.Tcp + protocol: ecs.Protocol.TCP }); new ecs.Ec2Service(stack, "FrontendService", { cluster, taskDefinition: frontendTD, - serviceDiscoveryOptions: { + cloudMapOptions: { name: "frontend" } }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json index efd377829f8ce..e9a6fd0d4d524 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json @@ -671,7 +671,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -910,4 +910,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts index 63aec63f1f896..b915e8d3f383b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts @@ -31,13 +31,13 @@ const frontend = frontendTD.addContainer('frontend', { frontend.addPortMappings({ containerPort: 80, hostPort: 80, - protocol: ecs.Protocol.Tcp + protocol: ecs.Protocol.TCP }); new ecs.Ec2Service(stack, "FrontendService", { cluster, taskDefinition: frontendTD, - serviceDiscoveryOptions: { + cloudMapOptions: { name: "frontend" } }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 571ca2579cd0e..a01775e6c06ec 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -6,6 +6,7 @@ import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../../lib'); import { BinPackResource, BuiltInAttributes, ContainerImage, NamespaceType, NetworkMode } from '../../lib'; +import { PlacementConstraint, PlacementStrategy } from '../../lib/placement'; export = { "When creating an ECS Service": { @@ -221,7 +222,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetType: ec2.SubnetType.Public + subnetType: ec2.SubnetType.PUBLIC } }); }); @@ -327,7 +328,7 @@ export = { cluster, taskDefinition, vpcSubnets: { - subnetType: ec2.SubnetType.Public + subnetType: ec2.SubnetType.PUBLIC } }); @@ -383,7 +384,7 @@ export = { taskDefinition }); - service.placeOnMemberOf("attribute:ecs.instance-type =~ t2.*"); + service.addPlacementConstraints(PlacementConstraint.memberOf("attribute:ecs.instance-type =~ t2.*")); // THEN expect(stack).to(haveResource("AWS::ECS::Service", { @@ -396,7 +397,7 @@ export = { test.done(); }, - "with placeSpreadAcross placement strategy"(test: Test) { + "with spreadAcross placement strategy"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -414,7 +415,7 @@ export = { taskDefinition }); - service.placeSpreadAcross(BuiltInAttributes.AvailabilityZone); + service.addPlacementStrategies(PlacementStrategy.spreadAcross(BuiltInAttributes.AvailabilityZone)); // THEN expect(stack).to(haveResource("AWS::ECS::Service", { @@ -427,7 +428,7 @@ export = { test.done(); }, - "errors with placeSpreadAcross placement strategy if daemon specified"(test: Test) { + "errors with spreadAcross placement strategy if daemon specified"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -448,13 +449,13 @@ export = { // THEN test.throws(() => { - service.placeSpreadAcross(BuiltInAttributes.AvailabilityZone); + service.addPlacementStrategies(PlacementStrategy.spreadAcross(BuiltInAttributes.AvailabilityZone)); }); test.done(); }, - "with placeRandomly placement strategy"(test: Test) { + "with random placement strategy"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); @@ -472,7 +473,7 @@ export = { taskDefinition }); - service.placeRandomly(); + service.addPlacementStrategies(PlacementStrategy.randomly()); // THEN expect(stack).to(haveResource("AWS::ECS::Service", { @@ -484,7 +485,7 @@ export = { test.done(); }, - "errors with placeRandomly placement strategy if daemon specified"(test: Test) { + "errors with random placement strategy if daemon specified"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc'); @@ -505,13 +506,13 @@ export = { // THEN test.throws(() => { - service.placeRandomly(); + service.addPlacementStrategies(PlacementStrategy.randomly()); }); test.done(); }, - "with placePackedBy placement strategy"(test: Test) { + "with packedBy placement strategy"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -529,7 +530,7 @@ export = { taskDefinition }); - service.placePackedBy(BinPackResource.Memory); + service.addPlacementStrategies(PlacementStrategy.packedBy(BinPackResource.MEMORY)); // THEN expect(stack).to(haveResource("AWS::ECS::Service", { @@ -542,7 +543,7 @@ export = { test.done(); }, - "errors with placePackedBy placement strategy if daemon specified"(test: Test) { + "errors with packedBy placement strategy if daemon specified"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'MyVpc', {}); @@ -563,7 +564,7 @@ export = { // THEN test.throws(() => { - service.placePackedBy(BinPackResource.Memory); + service.addPlacementStrategies(PlacementStrategy.packedBy(BinPackResource.MEMORY)); }); test.done(); @@ -597,11 +598,17 @@ export = { ContainerPort: 808, LoadBalancerName: { Ref: "LB8A12904C" } } - ], + ] + })); + + expect(stack).to(haveResource('AWS::ECS::Service', { + // if any load balancer is configured and healthCheckGracePeriodSeconds is not + // set, then it should default to 60 seconds. + HealthCheckGracePeriodSeconds: 60 })); test.done(); - }, + } }, 'When enabling service discovery': { @@ -625,7 +632,7 @@ export = { new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -656,7 +663,7 @@ export = { new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -683,13 +690,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -760,13 +767,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -842,7 +849,7 @@ export = { new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', dnsRecordType: cloudmap.DnsRecordType.A } @@ -871,13 +878,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -946,13 +953,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.Ec2Service(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', dnsRecordType: cloudmap.DnsRecordType.SRV } @@ -1031,7 +1038,7 @@ export = { }, namespace: 'AWS/ECS', metricName: 'MemoryUtilization', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Average' }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index 51669be9b32a9..aa56762d8a96d 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -58,7 +58,7 @@ export = { container.addUlimits({ hardLimit: 128, - name: ecs.UlimitName.Rss, + name: ecs.UlimitName.RSS, softLimit: 128 }); @@ -75,7 +75,7 @@ export = { PortMappings: [{ ContainerPort: 3000, HostPort: 0, - Protocol: Protocol.Tcp + Protocol: Protocol.TCP }], Ulimits: [{ HardLimit: 128, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json index 307aabd37bcd9..4cc54ebaaac04 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json @@ -423,6 +423,7 @@ "MinimumHealthyPercent": 50 }, "DesiredCount": 1, + "HealthCheckGracePeriodSeconds": 60, "LaunchType": "FARGATE", "LoadBalancers": [ { diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts index 17361f85235ff..c21f8c1072087 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.ts @@ -21,7 +21,7 @@ const container = taskDefinition.addContainer('web', { container.addPortMappings({ containerPort: 80, - protocol: ecs.Protocol.Tcp + protocol: ecs.Protocol.TCP }); const service = new ecs.FargateService(stack, "Service", { diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index d050d3b3f9987..8c52f4391e351 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -171,7 +171,7 @@ export = { new ecs.FargateService(stack, 'Svc', { cluster, taskDefinition, - healthCheckGracePeriodSeconds: 10 + healthCheckGracePeriod: cdk.Duration.seconds(10) }); // THEN @@ -251,6 +251,12 @@ export = { } })); + expect(stack).to(haveResource('AWS::ECS::Service', { + // if any load balancer is configured and healthCheckGracePeriodSeconds is not + // set, then it should default to 60 seconds. + HealthCheckGracePeriodSeconds: 60 + })); + test.done(); }, @@ -338,7 +344,7 @@ export = { new ecs.FargateService(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', } }); @@ -361,13 +367,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.FargateService(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp' } }); @@ -421,13 +427,13 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: 'foo.com', - type: NamespaceType.PrivateDns + type: NamespaceType.PRIVATE_DNS }); new ecs.FargateService(stack, 'Service', { cluster, taskDefinition, - serviceDiscoveryOptions: { + cloudMapOptions: { name: 'myApp', dnsRecordType: cloudmap.DnsRecordType.SRV } @@ -490,7 +496,7 @@ export = { }, namespace: 'AWS/ECS', metricName: 'CPUUtilization', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Average' }); diff --git a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts index 159205c50d31b..f1f5b41998ea7 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.aws-log-driver.ts @@ -12,14 +12,14 @@ export = { // WHEN const driver = new ecs.AwsLogDriver(stack, 'Log', { datetimeFormat: 'format', - logRetentionDays: logs.RetentionDays.OneMonth, + logRetention: logs.RetentionDays.ONE_MONTH, multilinePattern: 'pattern', streamPrefix: 'hello' }); // THEN expect(stack).to(haveResource('AWS::Logs::LogGroup', { - RetentionInDays: logs.RetentionDays.OneMonth + RetentionInDays: logs.RetentionDays.ONE_MONTH })); test.deepEqual( @@ -74,7 +74,7 @@ export = { // THEN test.throws(() => new ecs.AwsLogDriver(stack, 'Log', { logGroup, - logRetentionDays: logs.RetentionDays.FiveDays, + logRetention: logs.RetentionDays.FIVE_DAYS, streamPrefix: 'hello' }), /`logGroup`.*`logRetentionDays`/); diff --git a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts index e0661b909e510..ccf9a0ee2c78d 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.container-definition.ts @@ -372,9 +372,9 @@ export = { memoryLimitMiB: 1024, healthCheck: { command: [hcCommand], - intervalSeconds: 20, + interval: cdk.Duration.seconds(20), retries: 5, - startPeriod: 10 + startPeriod: cdk.Duration.seconds(10) } }); @@ -408,9 +408,9 @@ export = { memoryLimitMiB: 1024, healthCheck: { command: ["CMD-SHELL", hcCommand], - intervalSeconds: 20, + interval: cdk.Duration.seconds(20), retries: 5, - startPeriod: 10 + startPeriod: cdk.Duration.seconds(10) } }); @@ -444,9 +444,9 @@ export = { memoryLimitMiB: 1024, healthCheck: { command: ["CMD", hcCommand], - intervalSeconds: 20, + interval: cdk.Duration.seconds(20), retries: 5, - startPeriod: 10 + startPeriod: cdk.Duration.seconds(10) } }); @@ -522,8 +522,8 @@ export = { sharedMemorySize: 1024, }); - linuxParameters.addCapabilities(ecs.Capability.All); - linuxParameters.dropCapabilities(ecs.Capability.Kill); + linuxParameters.addCapabilities(ecs.Capability.ALL); + linuxParameters.dropCapabilities(ecs.Capability.KILL); // WHEN taskDefinition.addContainer('cont', { @@ -564,7 +564,7 @@ export = { sharedMemorySize: 1024, }); - linuxParameters.addCapabilities(ecs.Capability.All); + linuxParameters.addCapabilities(ecs.Capability.ALL); // WHEN taskDefinition.addContainer('cont', { @@ -574,7 +574,7 @@ export = { }); // Mutate linuxParameter after added to a container - linuxParameters.dropCapabilities(ecs.Capability.Setuid); + linuxParameters.dropCapabilities(ecs.Capability.SETUID); // THEN expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts index 40ebfd0db6377..04333445f8ce1 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts @@ -26,7 +26,7 @@ export = { CidrBlock: '10.0.0.0/16', EnableDnsHostnames: true, EnableDnsSupport: true, - InstanceTenancy: ec2.DefaultInstanceTenancy.Default, + InstanceTenancy: ec2.DefaultInstanceTenancy.DEFAULT, Tags: [ { Key: "Name", @@ -228,7 +228,7 @@ export = { cluster.addCapacity('GpuAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro'), machineImage: new ecs.EcsOptimizedAmi({ - hardwareType: ecs.AmiHardwareType.Gpu + hardwareType: ecs.AmiHardwareType.GPU }), }); @@ -254,8 +254,8 @@ export = { cluster.addCapacity('GpuAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro'), machineImage: new ecs.EcsOptimizedAmi({ - generation: ec2.AmazonLinuxGeneration.AmazonLinux, - hardwareType: ecs.AmiHardwareType.Gpu, + generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX, + hardwareType: ecs.AmiHardwareType.GPU, }), }); }); @@ -321,7 +321,7 @@ export = { // WHEN cluster.addDefaultCloudMapNamespace({ name: "foo.com", - type: ecs.NamespaceType.PublicDns + type: ecs.NamespaceType.PUBLIC_DNS }); // THEN @@ -381,7 +381,7 @@ export = { }); // THEN - test.equal(cluster2.defaultNamespace!.type, cloudmap.NamespaceType.DnsPrivate); + test.equal(cluster2.defaultNamespace!.type, cloudmap.NamespaceType.DNS_PRIVATE); test.deepEqual(stack2.resolve(cluster2.defaultNamespace!.namespaceId), 'import-namespace-id'); // Can retrieve subnets from VPC - will throw 'There are no 'Private' subnets in this VPC. Use a different VPC subnet selection.' if broken. diff --git a/packages/@aws-cdk/aws-eks/lib/ami.ts b/packages/@aws-cdk/aws-eks/lib/ami.ts index c7d4a868b6921..8f0eae05cf479 100644 --- a/packages/@aws-cdk/aws-eks/lib/ami.ts +++ b/packages/@aws-cdk/aws-eks/lib/ami.ts @@ -22,13 +22,13 @@ export interface EksOptimizedAmiProps { /** * Source for EKS optimized AMIs */ -export class EksOptimizedAmi extends ec2.GenericLinuxImage implements ec2.IMachineImageSource { +export class EksOptimizedAmi extends ec2.GenericLinuxImage implements ec2.IMachineImage { constructor(props: EksOptimizedAmiProps = {}) { const version = props.kubernetesVersion || LATEST_KUBERNETES_VERSION; if (!(version in EKS_AMI)) { throw new Error(`We don't have an AMI for kubernetes version ${version}`); } - super(EKS_AMI[version][props.nodeType || NodeType.Normal]); + super(EKS_AMI[version][props.nodeType || NodeType.NORMAL]); } } @@ -41,7 +41,7 @@ export enum NodeType { /** * Normal instances */ - Normal = 'Normal', + NORMAL = 'Normal', /** * GPU instances @@ -50,7 +50,7 @@ export enum NodeType { } export function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { - return instanceType.toString().startsWith('p2') || instanceType.toString().startsWith('p3') ? NodeType.GPU : NodeType.Normal; + return instanceType.toString().startsWith('p2') || instanceType.toString().startsWith('p3') ? NodeType.GPU : NodeType.NORMAL; } /** @@ -119,6 +119,6 @@ function parseTable(contents: string): {[type: string]: {[region: string]: strin return { [NodeType.GPU]: gpuTable, - [NodeType.Normal]: normalTable + [NodeType.NORMAL]: normalTable }; } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 17f93dc3b1acd..b6c4d1d2274dc 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -2,7 +2,7 @@ import autoscaling = require('@aws-cdk/aws-autoscaling'); import ec2 = require('@aws-cdk/aws-ec2'); import { Subnet } from '@aws-cdk/aws-ec2'; import iam = require('@aws-cdk/aws-iam'); -import { CfnOutput, Construct, IResource, Resource, Tag } from '@aws-cdk/cdk'; +import { CfnOutput, Construct, IResource, PhysicalName, Resource, Tag } from '@aws-cdk/cdk'; import { EksOptimizedAmi, nodeTypeForInstanceType } from './ami'; import { CfnCluster } from './eks.generated'; import { maxPodsForInstanceType } from './instance-data'; @@ -110,7 +110,7 @@ export interface ClusterProps { * * @default Automatically generated name */ - readonly clusterName?: string; + readonly clusterName?: PhysicalName; /** * Security Group to use for Control Plane ENIs @@ -199,7 +199,9 @@ export class Cluster extends Resource implements ICluster { * @param props properties in the IClusterProps interface */ constructor(scope: Construct, id: string, props: ClusterProps) { - super(scope, id); + super(scope, id, { + physicalName: props.clusterName, + }); this.vpc = props.vpc; this.version = props.version; @@ -221,15 +223,15 @@ export class Cluster extends Resource implements ICluster { this.connections = new ec2.Connections({ securityGroups: [securityGroup], - defaultPortRange: new ec2.TcpPort(443), // Control Plane has an HTTPS API + defaultPort: ec2.Port.tcp(443), // Control Plane has an HTTPS API }); // Get subnetIds for all selected subnets - const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; + const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE }]; const subnetIds = [...new Set(Array().concat(...placements.map(s => props.vpc.selectSubnets(s).subnetIds)))]; const resource = new CfnCluster(this, 'Resource', { - name: props.clusterName, + name: this.physicalName, roleArn: this.role.roleArn, version: props.version, resourcesVpcConfig: { @@ -238,12 +240,22 @@ export class Cluster extends Resource implements ICluster { } }); - this.clusterName = resource.refAsString; - this.clusterArn = resource.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.attrArn, + name: resource.ref, + arnComponents: { + service: 'eks', + resource: 'cluster', + resourceName: this.physicalName, + }, + }); + this.clusterName = resourceIdentifiers.name; + this.clusterArn = resourceIdentifiers.arn; + this.clusterEndpoint = resource.attrEndpoint; this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData; - new CfnOutput(this, 'ClusterName', { value: this.clusterName, disableExport: true }); + new CfnOutput(this, 'ClusterName', { value: this.clusterName }); } /** @@ -260,7 +272,7 @@ export class Cluster extends Resource implements ICluster { nodeType: nodeTypeForInstanceType(options.instanceType), kubernetesVersion: this.version, }), - updateType: options.updateType || autoscaling.UpdateType.RollingUpdate, + updateType: options.updateType || autoscaling.UpdateType.ROLLING_UPDATE, instanceType: options.instanceType, }); @@ -287,19 +299,19 @@ export class Cluster extends Resource implements ICluster { */ public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AddAutoScalingGroupOptions) { // self rules - autoScalingGroup.connections.allowInternally(new ec2.AllTraffic()); + autoScalingGroup.connections.allowInternally(ec2.Port.allTraffic()); // Cluster to:nodes rules - autoScalingGroup.connections.allowFrom(this, new ec2.TcpPort(443)); - autoScalingGroup.connections.allowFrom(this, new ec2.TcpPortRange(1025, 65535)); + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcp(443)); + autoScalingGroup.connections.allowFrom(this, ec2.Port.tcpRange(1025, 65535)); // Allow HTTPS from Nodes to Cluster - autoScalingGroup.connections.allowTo(this, new ec2.TcpPort(443)); + autoScalingGroup.connections.allowTo(this, ec2.Port.tcp(443)); // Allow all node outbound traffic - autoScalingGroup.connections.allowToAnyIPv4(new ec2.TcpAllPorts()); - autoScalingGroup.connections.allowToAnyIPv4(new ec2.UdpAllPorts()); - autoScalingGroup.connections.allowToAnyIPv4(new ec2.IcmpAllTypesAndCodes()); + autoScalingGroup.connections.allowToAnyIPv4(ec2.Port.allTcp()); + autoScalingGroup.connections.allowToAnyIPv4(ec2.Port.allUdp()); + autoScalingGroup.connections.allowToAnyIPv4(ec2.Port.allIcmp()); autoScalingGroup.addUserData( 'set -o xtrace', @@ -317,7 +329,6 @@ export class Cluster extends Resource implements ICluster { // Create an CfnOutput for the Instance Role ARN (need to paste it into aws-auth-cm.yaml) new CfnOutput(autoScalingGroup, 'InstanceRoleARN', { - disableExport: true, value: autoScalingGroup.role.roleArn }); } diff --git a/packages/@aws-cdk/aws-eks/lib/instance-data.ts b/packages/@aws-cdk/aws-eks/lib/instance-data.ts index 137bcbdd6cab0..c2ae3084938e6 100644 --- a/packages/@aws-cdk/aws-eks/lib/instance-data.ts +++ b/packages/@aws-cdk/aws-eks/lib/instance-data.ts @@ -7,6 +7,18 @@ import ec2 = require('@aws-cdk/aws-ec2'); */ const MAX_PODS = Object.freeze( new Map([ + ['a1.medium', 8], + ['a1.large', 29], + ['a1.xlarge', 58], + ['a1.2xlarge', 58], + ['a1.4xlarge', 234], + ['c1.medium', 12], + ['c1.xlarge', 58], + ['c3.large', 29], + ['c3.xlarge', 58], + ['c3.2xlarge', 58], + ['c3.4xlarge', 234], + ['c3.8xlarge', 234], ['c4.large', 29], ['c4.xlarge', 58], ['c4.2xlarge', 58], @@ -18,12 +30,63 @@ const MAX_PODS = Object.freeze( ['c5.4xlarge', 234], ['c5.9xlarge', 234], ['c5.18xlarge', 737], + ['c5d.large', 29], + ['c5d.xlarge', 58], + ['c5d.2xlarge', 58], + ['c5d.4xlarge', 234], + ['c5d.9xlarge', 234], + ['c5d.18xlarge', 737], + ['c5n.large', 29], + ['c5n.xlarge', 58], + ['c5n.2xlarge', 58], + ['c5n.4xlarge', 234], + ['c5n.9xlarge', 234], + ['c5n.18xlarge', 737], + ['cc2.8xlarge', 234], + ['cr1.8xlarge', 234], + ['d2.xlarge', 58], + ['d2.2xlarge', 58], + ['d2.4xlarge', 234], + ['d2.8xlarge', 234], + ['f1.2xlarge', 58], + ['f1.4xlarge', 234], + ['f1.16xlarge', 242], + ['g2.2xlarge', 58], + ['g2.8xlarge', 234], + ['g3s.xlarge', 58], + ['g3.4xlarge', 234], + ['g3.8xlarge', 234], + ['g3.16xlarge', 452], + ['h1.2xlarge', 58], + ['h1.4xlarge', 234], + ['h1.8xlarge', 234], + ['h1.16xlarge', 452], + ['hs1.8xlarge', 234], + ['i2.xlarge', 58], + ['i2.2xlarge', 58], + ['i2.4xlarge', 234], + ['i2.8xlarge', 234], ['i3.large', 29], ['i3.xlarge', 58], ['i3.2xlarge', 58], ['i3.4xlarge', 234], ['i3.8xlarge', 234], - ['i3.16xlarge', 737], + ['i3.16xlarge', 452], + ['i3.metal', 737], + ['i3en.large', 29], + ['i3en.xlarge', 58], + ['i3en.2xlarge', 58], + ['i3en.3xlarge', 58], + ['i3en.6xlarge', 234], + ['i3en.12xlarge', 234], + ['i3en.24xlarge', 737], + ['m1.small', 8], + ['m1.medium', 12], + ['m1.large', 29], + ['m1.xlarge', 58], + ['m2.xlarge', 58], + ['m2.2xlarge', 118], + ['m2.4xlarge', 234], ['m3.medium', 12], ['m3.large', 29], ['m3.xlarge', 58], @@ -33,18 +96,39 @@ const MAX_PODS = Object.freeze( ['m4.2xlarge', 58], ['m4.4xlarge', 234], ['m4.10xlarge', 234], + ['m4.16xlarge', 234], ['m5.large', 29], ['m5.xlarge', 58], ['m5.2xlarge', 58], ['m5.4xlarge', 234], ['m5.12xlarge', 234], ['m5.24xlarge', 737], + ['m5a.large', 29], + ['m5a.xlarge', 58], + ['m5a.2xlarge', 58], + ['m5a.4xlarge', 234], + ['m5a.12xlarge', 234], + ['m5a.24xlarge', 737], + ['m5ad.large', 29], + ['m5ad.xlarge', 58], + ['m5ad.2xlarge', 58], + ['m5ad.4xlarge', 234], + ['m5ad.12xlarge', 234], + ['m5ad.24xlarge', 737], + ['m5d.large', 29], + ['m5d.xlarge', 58], + ['m5d.2xlarge', 58], + ['m5d.4xlarge', 234], + ['m5d.12xlarge', 234], + ['m5d.24xlarge', 737], ['p2.xlarge', 58], ['p2.8xlarge', 234], ['p2.16xlarge', 234], ['p3.2xlarge', 58], ['p3.8xlarge', 234], ['p3.16xlarge', 234], + ['p3dn.24xlarge', 737], + ['r3.large', 29], ['r3.xlarge', 58], ['r3.2xlarge', 58], ['r3.4xlarge', 234], @@ -54,14 +138,67 @@ const MAX_PODS = Object.freeze( ['r4.2xlarge', 58], ['r4.4xlarge', 234], ['r4.8xlarge', 234], - ['r4.16xlarge', 735], - ['t2.small', 8], + ['r4.16xlarge', 452], + ['r5.large', 29], + ['r5.xlarge', 58], + ['r5.2xlarge', 58], + ['r5.4xlarge', 234], + ['r5.12xlarge', 234], + ['r5.24xlarge', 737], + ['r5a.large', 29], + ['r5a.xlarge', 58], + ['r5a.2xlarge', 58], + ['r5a.4xlarge', 234], + ['r5a.12xlarge', 234], + ['r5a.24xlarge', 737], + ['r5ad.large', 29], + ['r5ad.xlarge', 58], + ['r5ad.2xlarge', 58], + ['r5ad.4xlarge', 234], + ['r5ad.12xlarge', 234], + ['r5ad.24xlarge', 737], + ['r5d.large', 29], + ['r5d.xlarge', 58], + ['r5d.2xlarge', 58], + ['r5d.4xlarge', 234], + ['r5d.12xlarge', 234], + ['r5d.24xlarge', 737], + ['t1.micro', 4], + ['t2.nano', 4], + ['t2.micro', 4], + ['t2.small', 11], ['t2.medium', 17], ['t2.large', 35], ['t2.xlarge', 44], ['t2.2xlarge', 44], + ['t3.nano', 4], + ['t3.micro', 4], + ['t3.small', 11], + ['t3.medium', 17], + ['t3.large', 35], + ['t3.xlarge', 58], + ['t3.2xlarge', 58], + ['t3a.nano', 4], + ['t3a.micro', 4], + ['t3a.small', 11], + ['t3a.medium', 17], + ['t3a.large', 35], + ['t3a.xlarge', 58], + ['t3a.2xlarge', 58], ['x1.16xlarge', 234], ['x1.32xlarge', 234], + ['x1e.xlarge', 29], + ['x1e.2xlarge', 58], + ['x1e.4xlarge', 58], + ['x1e.8xlarge', 58], + ['x1e.16xlarge', 234], + ['x1e.32xlarge', 234], + ['z1d.large', 29], + ['z1d.xlarge', 58], + ['z1d.2xlarge', 58], + ['z1d.3xlarge', 234], + ['z1d.6xlarge', 234], + ['z1d.12xlarge', 737], ]), ); diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index 5d3c275b9e88b..e60dae8ac62bf 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -15,12 +15,12 @@ class EksClusterStack extends cdk.Stack { /// !show const asg = cluster.addCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, keyName: 'my-key-name', }); // Replace with desired IP - asg.connections.allowFrom(new ec2.CidrIPv4('1.2.3.4/32'), new ec2.TcpPort(22)); + asg.connections.allowFrom(ec2.Peer.ipv4('1.2.3.4/32'), ec2.Port.tcp(22)); /// !hide } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index a868047bbc65e..e8d1be9124478 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -1,7 +1,5 @@ -import { - AnyIPv4, Connections, IConnectable, IPortRange, ISecurityGroup, - ISubnet, IVpc, SecurityGroup, TcpPort } from '@aws-cdk/aws-ec2'; -import { Construct, Lazy, Resource } from '@aws-cdk/cdk'; +import { Connections, IConnectable, ISecurityGroup, ISubnet, IVpc, Peer, Port, SecurityGroup } from '@aws-cdk/aws-ec2'; +import { Construct, Duration, Lazy, Resource } from '@aws-cdk/cdk'; import { CfnLoadBalancer } from './elasticloadbalancing.generated'; /** @@ -106,16 +104,16 @@ export interface HealthCheck { /** * Number of seconds between health checks * - * @default 30 + * @default Duration.seconds(30) */ - readonly interval?: number; + readonly interval?: Duration; /** * Health check timeout * - * @default 5 + * @default Duration.seconds(5) */ - readonly timeout?: number; + readonly timeout?: Duration; } /** @@ -192,10 +190,10 @@ export interface LoadBalancerListener { } export enum LoadBalancingProtocol { - Tcp = 'tcp', - Ssl = 'ssl', - Http = 'http', - Https = 'https' + TCP = 'tcp', + SSL = 'ssl', + HTTP = 'http', + HTTPS = 'https' } /** @@ -256,7 +254,7 @@ export class LoadBalancer extends Resource implements IConnectable { const instancePort = listener.internalPort || listener.externalPort; const instanceProtocol = ifUndefined(listener.internalProtocol, ifUndefined(tryWellKnownProtocol(instancePort), - isHttpProtocol(protocol) ? LoadBalancingProtocol.Http : LoadBalancingProtocol.Tcp)); + isHttpProtocol(protocol) ? LoadBalancingProtocol.HTTP : LoadBalancingProtocol.TCP)); this.listeners.push({ loadBalancerPort: listener.externalPort.toString(), @@ -267,10 +265,10 @@ export class LoadBalancer extends Resource implements IConnectable { policyNames: listener.policyNames }); - const port = new ListenerPort(this.securityGroup, new TcpPort(listener.externalPort)); + const port = new ListenerPort(this.securityGroup, Port.tcp(listener.externalPort)); // Allow connections on the public port for all supplied peers (default: everyone) - ifUndefined(listener.allowConnectionsFrom, [new AnyIPv4()]).forEach(peer => { + ifUndefined(listener.allowConnectionsFrom, [Peer.anyIpv4()]).forEach(peer => { port.connections.allowDefaultPortFrom(peer, `Default rule allow on ${listener.externalPort}`); }); @@ -292,7 +290,7 @@ export class LoadBalancer extends Resource implements IConnectable { * @attribute */ public get loadBalancerName() { - return this.elb.refAsString; + return this.elb.ref; } /** @@ -356,7 +354,7 @@ export class LoadBalancer extends Resource implements IConnectable { private allowTargetConnection(instancePort: number, target: ILoadBalancerTarget) { this.connections.allowTo( target, - new TcpPort(instancePort), + Port.tcp(instancePort), `Port ${instancePort} LB to fleet`); } } @@ -377,8 +375,8 @@ export class LoadBalancer extends Resource implements IConnectable { export class ListenerPort implements IConnectable { public readonly connections: Connections; - constructor(securityGroup: ISecurityGroup, defaultPortRange: IPortRange) { - this.connections = new Connections({ securityGroups: [securityGroup] , defaultPortRange }); + constructor(securityGroup: ISecurityGroup, defaultPort: Port) { + this.connections = new Connections({ securityGroups: [securityGroup], defaultPort }); } } @@ -391,13 +389,13 @@ function wellKnownProtocol(port: number): LoadBalancingProtocol { } function tryWellKnownProtocol(port: number): LoadBalancingProtocol | undefined { - if (port === 80) { return LoadBalancingProtocol.Http; } - if (port === 443) { return LoadBalancingProtocol.Https; } + if (port === 80) { return LoadBalancingProtocol.HTTP; } + if (port === 443) { return LoadBalancingProtocol.HTTPS; } return undefined; } function isHttpProtocol(proto: LoadBalancingProtocol): boolean { - return proto === LoadBalancingProtocol.Https || proto === LoadBalancingProtocol.Http; + return proto === LoadBalancingProtocol.HTTPS || proto === LoadBalancingProtocol.HTTP; } function ifUndefined(x: T | undefined, def: T): T { @@ -414,17 +412,17 @@ function ifUndefinedLazy(x: T | undefined, def: () => T): T { function healthCheckToJSON(healthCheck: HealthCheck): CfnLoadBalancer.HealthCheckProperty { const protocol = ifUndefined(healthCheck.protocol, ifUndefined(tryWellKnownProtocol(healthCheck.port), - LoadBalancingProtocol.Tcp)); + LoadBalancingProtocol.TCP)); - const path = protocol === LoadBalancingProtocol.Http || protocol === LoadBalancingProtocol.Https ? ifUndefined(healthCheck.path, "/") : ""; + const path = protocol === LoadBalancingProtocol.HTTP || protocol === LoadBalancingProtocol.HTTPS ? ifUndefined(healthCheck.path, "/") : ""; const target = `${protocol.toUpperCase()}:${healthCheck.port}${path}`; return { healthyThreshold: ifUndefined(healthCheck.healthyThreshold, 2).toString(), - interval: ifUndefined(healthCheck.interval, 30).toString(), + interval: (healthCheck.interval || Duration.seconds(30)).toSeconds().toString(), target, - timeout: ifUndefined(healthCheck.timeout, 5).toString(), + timeout: (healthCheck.timeout || Duration.seconds(5)).toSeconds().toString(), unhealthyThreshold: ifUndefined(healthCheck.unhealthyThreshold, 5).toString(), }; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index 164e8319ac3b0..6856d8b6740ce 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -81,5 +81,10 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-elasticloadbalancing.LoadBalancerProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.ts index 6c09efc21375b..d28f53088154c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/integ.elb.ts @@ -15,7 +15,7 @@ new elb.LoadBalancer(stack, 'LB', { internetFacing: true, listeners: [{ externalPort: 80, - allowConnectionsFrom: [new ec2.AnyIPv4()] + allowConnectionsFrom: [ec2.Peer.anyIpv4()] }], healthCheck: { port: 80 diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts index e228d4bce9f35..fac680a0d1d1e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/test/test.loadbalancer.ts @@ -1,6 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { CidrIPv4, Connections, Vpc } from '@aws-cdk/aws-ec2'; -import { Stack } from '@aws-cdk/cdk'; +import { Connections, Peer, Vpc } from '@aws-cdk/aws-ec2'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ILoadBalancerTarget, LoadBalancer, LoadBalancingProtocol } from '../lib'; @@ -13,9 +13,9 @@ export = { const lb = new LoadBalancer(stack, 'LB', { vpc }); lb.addListener({ - externalProtocol: LoadBalancingProtocol.Http, + externalProtocol: LoadBalancingProtocol.HTTP, externalPort: 8080, - internalProtocol: LoadBalancingProtocol.Http, + internalProtocol: LoadBalancingProtocol.HTTP, internalPort: 8080, }); @@ -40,9 +40,9 @@ export = { new LoadBalancer(stack, 'LB', { vpc, healthCheck: { - interval: 60, + interval: Duration.minutes(1), path: '/ping', - protocol: LoadBalancingProtocol.Https, + protocol: LoadBalancingProtocol.HTTPS, port: 443, } }); @@ -68,9 +68,9 @@ export = { const elb = new LoadBalancer(stack, 'LB', { vpc, healthCheck: { - interval: 60, + interval: Duration.minutes(1), path: '/ping', - protocol: LoadBalancingProtocol.Https, + protocol: LoadBalancingProtocol.HTTPS, port: 443, } }); @@ -155,7 +155,7 @@ export = { class FakeTarget implements ILoadBalancerTarget { public readonly connections = new Connections({ - securityGroupRule: new CidrIPv4('666.666.666.666/666') + peer: Peer.ipv4('666.666.666.666/666') }); public attachToClassicLB(_loadBalancer: LoadBalancer): void { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index 200f0e9852aef..a52d4351cc35b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -147,7 +147,7 @@ export class ApplicationListenerRule extends cdk.Construct { this.addFixedResponse(props.fixedResponse); } - this.listenerRuleArn = resource.refAsString; + this.listenerRuleArn = resource.ref; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index adb38a833cffc..0e8071e7366a8 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -1,5 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, Lazy, Resource } from '@aws-cdk/cdk'; import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { ApplicationProtocol, SslPolicy } from '../shared/enums'; @@ -127,13 +127,13 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // but adds its own default port. this.connections = new ec2.Connections({ securityGroups: props.loadBalancer.connections.securityGroups, - defaultPortRange: new ec2.TcpPort(port), + defaultPort: ec2.Port.tcp(port), }); (props.defaultTargetGroups || []).forEach(this.addDefaultTargetGroup.bind(this)); if (props.open !== false) { - this.connections.allowDefaultPortFrom(new ec2.AnyIPv4(), `Allow from anyone on port ${port}`); + this.connections.allowDefaultPortFrom(ec2.Peer.anyIpv4(), `Allow from anyone on port ${port}`); } } @@ -190,12 +190,12 @@ export class ApplicationListener extends BaseListener implements IApplicationLis } const group = new ApplicationTargetGroup(this, id + 'Group', { - deregistrationDelaySec: props.deregistrationDelaySec, + deregistrationDelay: props.deregistrationDelay, healthCheck: props.healthCheck, port: props.port, protocol: props.protocol, - slowStartSec: props.slowStartSec, - stickinessCookieDurationSec: props.stickinessCookieDurationSec, + slowStart: props.slowStart, + stickinessCookieDuration: props.stickinessCookieDuration, targetGroupName: props.targetGroupName, targets: props.targets, vpc: this.loadBalancer.vpc, @@ -245,7 +245,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis * * Don't call this directly. It is called by ApplicationTargetGroup. */ - public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void { + public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.Port): void { this.connections.allowTo(connectable, portRange, 'Load balancer to target'); } @@ -310,7 +310,7 @@ export interface IApplicationListener extends IResource, ec2.IConnectable { * * Don't call this directly. It is called by ApplicationTargetGroup. */ - registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void; + registerConnectable(connectable: ec2.IConnectable, portRange: ec2.Port): void; } /** @@ -346,11 +346,11 @@ class ImportedApplicationListener extends Resource implements IApplicationListen this.listenerArn = props.listenerArn; - const defaultPortRange = props.defaultPort !== undefined ? new ec2.TcpPort(props.defaultPort) : undefined; + const defaultPort = props.defaultPort !== undefined ? ec2.Port.tcp(props.defaultPort) : undefined; this.connections = new ec2.Connections({ securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroup', props.securityGroupId)], - defaultPortRange, + defaultPort, }); } @@ -410,7 +410,7 @@ class ImportedApplicationListener extends Resource implements IApplicationListen * * Don't call this directly. It is called by ApplicationTargetGroup. */ - public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.IPortRange): void { + public registerConnectable(connectable: ec2.IConnectable, portRange: ec2.Port): void { this.connections.allowTo(connectable, portRange, 'Load balancer to target'); } } @@ -491,11 +491,11 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * The time period during which the load balancer sends a newly registered * target a linearly increasing share of the traffic to the target group. * - * The range is 30–900 seconds (15 minutes). + * The range is 30-900 seconds (15 minutes). * * @default 0 */ - readonly slowStartSec?: number; + readonly slowStart?: Duration; /** * The stickiness cookie expiration period. @@ -505,9 +505,9 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * After this period, the cookie is considered stale. The minimum value is * 1 second and the maximum value is 7 days (604800 seconds). * - * @default 86400 (1 day) + * @default Duration.days(1) */ - readonly stickinessCookieDurationSec?: number; + readonly stickinessCookieDuration?: Duration; /** * The targets to add to this target group. @@ -532,11 +532,11 @@ export interface AddApplicationTargetsProps extends AddRuleProps { /** * The amount of time for Elastic Load Balancing to wait before deregistering a target. * - * The range is 0–3600 seconds. + * The range is 0-3600 seconds. * - * @default 300 + * @default Duration.minutes(5) */ - readonly deregistrationDelaySec?: number; + readonly deregistrationDelay?: Duration; /** * Health check configuration diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 0d2d5e4a323f2..3c67a0a33fba8 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Lazy, Resource, Stack, Token } from '@aws-cdk/cdk'; +import { Construct, Duration, Lazy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { IpAddressType } from '../shared/enums'; import { ApplicationListener, BaseApplicationListenerProps } from './application-listener'; @@ -39,7 +39,7 @@ export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { * * @default 60 */ - readonly idleTimeoutSecs?: number; + readonly idleTimeout?: Duration; } /** @@ -52,7 +52,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic * Import an existing Application Load Balancer */ public static fromApplicationLoadBalancerAttributes( - scope: Construct, id: string, attrs: ApplicationLoadBalancerAttributes): IApplicationLoadBalancer { + scope: Construct, id: string, attrs: ApplicationLoadBalancerAttributes): IApplicationLoadBalancer { return new ImportedApplicationLoadBalancer(scope, id, attrs); } @@ -75,7 +75,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic this.connections = new ec2.Connections({ securityGroups: [this.securityGroup] }); if (props.http2Enabled === false) { this.setAttribute('routing.http2.enabled', 'false'); } - if (props.idleTimeoutSecs !== undefined) { this.setAttribute('idle_timeout.timeout_seconds', props.idleTimeoutSecs.toString()); } + if (props.idleTimeout !== undefined) { this.setAttribute('idle_timeout.timeout_seconds', props.idleTimeout.toSeconds().toString()); } } /** @@ -97,7 +97,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic } prefix = prefix || ''; - bucket.grantPut(new iam.AccountPrincipal(account), prefix + '*'); + bucket.grantPut(new iam.AccountPrincipal(account), `${(prefix ? prefix + "/" : "")}AWSLogs/${Stack.of(this).account}/*`); // make sure the bucket's policy is created before the ALB (see https://github.com/awslabs/aws-cdk/issues/1633) this.node.addDependency(bucket); @@ -519,7 +519,7 @@ export interface ApplicationLoadBalancerAttributes { } // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions -const ELBV2_ACCOUNTS: {[region: string]: string } = { +const ELBV2_ACCOUNTS: { [region: string]: string } = { 'us-east-1': '127311923021', 'us-east-2': '033677994240', 'us-west-1': '027434742980', diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 8df988f15f5a8..f49cb1d305a6f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -1,6 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); -import { Construct, IConstruct } from '@aws-cdk/cdk'; +import { Construct, Duration, IConstruct } from '@aws-cdk/cdk'; import { BaseTargetGroupProps, ITargetGroup, loadBalancerNameFromListenerArn, LoadBalancerTargetProps, TargetGroupBase, TargetGroupImportProps } from '../shared/base-target-group'; import { ApplicationProtocol } from '../shared/enums'; @@ -31,11 +31,11 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * The time period during which the load balancer sends a newly registered * target a linearly increasing share of the traffic to the target group. * - * The range is 30–900 seconds (15 minutes). + * The range is 30-900 seconds (15 minutes). * * @default 0 */ - readonly slowStartSec?: number; + readonly slowStart?: Duration; /** * The stickiness cookie expiration period. @@ -45,9 +45,9 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * After this period, the cookie is considered stale. The minimum value is * 1 second and the maximum value is 7 days (604800 seconds). * - * @default 86400 (1 day) + * @default Duration.days(1) */ - readonly stickinessCookieDurationSec?: number; + readonly stickinessCookieDuration?: Duration; /** * The targets to add to this target group. @@ -86,11 +86,11 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat this.connectableMembers = []; this.listeners = []; - if (props.slowStartSec !== undefined) { - this.setAttribute('slow_start.duration_seconds', props.slowStartSec.toString()); + if (props.slowStart !== undefined) { + this.setAttribute('slow_start.duration_seconds', props.slowStart.toSeconds().toString()); } - if (props.stickinessCookieDurationSec !== undefined) { - this.enableCookieStickiness(props.stickinessCookieDurationSec); + if (props.stickinessCookieDuration !== undefined) { + this.enableCookieStickiness(props.stickinessCookieDuration); } this.addTarget(...(props.targets || [])); @@ -109,10 +109,10 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat /** * Enable sticky routing via a cookie to members of this target group */ - public enableCookieStickiness(durationSec: number) { + public enableCookieStickiness(duration: Duration) { this.setAttribute('stickiness.enabled', 'true'); this.setAttribute('stickiness.type', 'lb_cookie'); - this.setAttribute('stickiness.lb_cookie.duration_seconds', durationSec.toString()); + this.setAttribute('stickiness.lb_cookie.duration_seconds', duration.toSeconds().toString()); } /** @@ -120,8 +120,8 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat * * Don't call this directly. It will be called by load balancing targets. */ - public registerConnectable(connectable: ec2.IConnectable, portRange?: ec2.IPortRange) { - portRange = portRange || new ec2.TcpPort(this.defaultPort); + public registerConnectable(connectable: ec2.IConnectable, portRange?: ec2.Port) { + portRange = portRange || ec2.Port.tcp(this.defaultPort); // Notify all listeners that we already know about of this new connectable. // Then remember for new listeners that might get added later. @@ -308,7 +308,7 @@ interface ConnectableMember { /** * The port (range) the member is listening on */ - portRange: ec2.IPortRange; + portRange: ec2.Port; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index b84c92ca8deaa..112616f85a8d3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, Resource } from '@aws-cdk/cdk'; import { BaseListener } from '../shared/base-listener'; import { HealthCheck } from '../shared/base-target-group'; import { Protocol, SslPolicy } from '../shared/enums'; @@ -140,7 +140,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { } const group = new NetworkTargetGroup(this, id + 'Group', { - deregistrationDelaySec: props.deregistrationDelaySec, + deregistrationDelay: props.deregistrationDelay, healthCheck: props.healthCheck, port: props.port, proxyProtocolV2: props.proxyProtocolV2, @@ -202,9 +202,9 @@ export interface AddNetworkTargetsProps { * * The range is 0-3600 seconds. * - * @default 300 + * @default Duration.minutes(5) */ - readonly deregistrationDelaySec?: number; + readonly deregistrationDelay?: Duration; /** * Indicates whether Proxy Protocol version 2 is enabled. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 406db3cd7ea33..298ea0739bbd9 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -21,7 +21,7 @@ export abstract class BaseListener extends Resource { defaultActions: Lazy.anyValue({ produce: () => this.defaultActions }), }); - this.listenerArn = resource.refAsString; + this.listenerArn = resource.ref; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index df5d192847a25..3bd137260dd55 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -1,5 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnLoadBalancer } from '../elasticloadbalancingv2.generated'; import { Attributes, ifUndefined, renderAttributes } from './util'; @@ -12,7 +12,7 @@ export interface BaseLoadBalancerProps { * * @default - Automatically generated name. */ - readonly loadBalancerName?: string; + readonly loadBalancerName?: PhysicalName; /** * The VPC network to place the load balancer in @@ -121,26 +121,28 @@ export abstract class BaseLoadBalancer extends Resource { private readonly attributes: Attributes = {}; constructor(scope: Construct, id: string, baseProps: BaseLoadBalancerProps, additionalProps: any) { - super(scope, id); + super(scope, id, { + physicalName: baseProps.loadBalancerName, + }); const internetFacing = ifUndefined(baseProps.internetFacing, false); const vpcSubnets = ifUndefined(baseProps.vpcSubnets, - { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); + { subnetType: internetFacing ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }); - const { subnetIds, internetConnectedDependency } = baseProps.vpc.selectSubnets(vpcSubnets); + const { subnetIds, internetConnectivityEstablished } = baseProps.vpc.selectSubnets(vpcSubnets); this.vpc = baseProps.vpc; const resource = new CfnLoadBalancer(this, 'Resource', { - name: baseProps.loadBalancerName, + name: this.physicalName, subnets: subnetIds, scheme: internetFacing ? 'internet-facing' : 'internal', loadBalancerAttributes: Lazy.anyValue({ produce: () => renderAttributes(this.attributes) }), ...additionalProps }); if (internetFacing) { - resource.node.addDependency(internetConnectedDependency); + resource.node.addDependency(internetConnectivityEstablished); } if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } @@ -149,7 +151,7 @@ export abstract class BaseLoadBalancer extends Resource { this.loadBalancerDnsName = resource.attrDnsName; this.loadBalancerFullName = resource.attrLoadBalancerFullName; this.loadBalancerName = resource.attrLoadBalancerName; - this.loadBalancerArn = resource.refAsString; + this.loadBalancerArn = resource.ref; this.loadBalancerSecurityGroups = resource.attrSecurityGroups; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index af25fbd28c211..e98066d7ab2f7 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -31,7 +31,7 @@ export interface BaseTargetGroupProps { * * @default 300 */ - readonly deregistrationDelaySec?: number; + readonly deregistrationDelay?: cdk.Duration; /** * Health check configuration @@ -59,9 +59,9 @@ export interface HealthCheck { /** * The approximate number of seconds between health checks for an individual target. * - * @default 30 + * @default Duration.seconds(30) */ - readonly intervalSecs?: number; + readonly interval?: cdk.Duration; /** * The ping path destination where Elastic Load Balancing sends health check requests. @@ -90,13 +90,13 @@ export interface HealthCheck { /** * The amount of time, in seconds, during which no response from a target means a failed health check. * - * For Application Load Balancers, the range is 2–60 seconds and the + * For Application Load Balancers, the range is 2-60 seconds and the * default is 5 seconds. For Network Load Balancers, this is 10 seconds for * TCP and HTTPS health checks and 6 seconds for HTTP health checks. * - * @default 5 for ALBs, 10 or 6 for NLBs + * @default Duration.seconds(5) for ALBs, Duration.seconds(10) or Duration.seconds(6) for NLBs */ - readonly timeoutSeconds?: number; + readonly timeout?: cdk.Duration; /** * The number of consecutive health checks successes required before considering an unhealthy target healthy. @@ -204,8 +204,8 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr constructor(scope: cdk.Construct, id: string, baseProps: BaseTargetGroupProps, additionalProps: any) { super(scope, id); - if (baseProps.deregistrationDelaySec !== undefined) { - this.setAttribute('deregistration_delay.timeout_seconds', baseProps.deregistrationDelaySec.toString()); + if (baseProps.deregistrationDelay !== undefined) { + this.setAttribute('deregistration_delay.timeout_seconds', baseProps.deregistrationDelay.toString()); } this.healthCheck = baseProps.healthCheck || {}; @@ -219,11 +219,15 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr vpcId: baseProps.vpc.vpcId, // HEALTH CHECK - healthCheckIntervalSeconds: cdk.Lazy.numberValue({ produce: () => this.healthCheck && this.healthCheck.intervalSecs }), + healthCheckIntervalSeconds: cdk.Lazy.numberValue({ + produce: () => this.healthCheck && this.healthCheck.interval && this.healthCheck.interval.toSeconds() + }), healthCheckPath: cdk.Lazy.stringValue({ produce: () => this.healthCheck && this.healthCheck.path }), healthCheckPort: cdk.Lazy.stringValue({ produce: () => this.healthCheck && this.healthCheck.port }), healthCheckProtocol: cdk.Lazy.stringValue({ produce: () => this.healthCheck && this.healthCheck.protocol }), - healthCheckTimeoutSeconds: cdk.Lazy.numberValue({ produce: () => this.healthCheck && this.healthCheck.timeoutSeconds }), + healthCheckTimeoutSeconds: cdk.Lazy.numberValue({ + produce: () => this.healthCheck && this.healthCheck.timeout && this.healthCheck.timeout.toSeconds() + }), healthyThresholdCount: cdk.Lazy.numberValue({ produce: () => this.healthCheck && this.healthCheck.healthyThresholdCount }), unhealthyThresholdCount: cdk.Lazy.numberValue({ produce: () => this.healthCheck && this.healthCheck.unhealthyThresholdCount }), matcher: cdk.Lazy.anyValue({ produce: () => this.healthCheck && this.healthCheck.healthyHttpCodes !== undefined ? { @@ -234,7 +238,7 @@ export abstract class TargetGroupBase extends cdk.Construct implements ITargetGr }); this.targetGroupLoadBalancerArns = this.resource.attrLoadBalancerArns; - this.targetGroupArn = this.resource.refAsString; + this.targetGroupArn = this.resource.ref; this.targetGroupFullName = this.resource.attrTargetGroupFullName; this.loadBalancerArns = this.resource.attrLoadBalancerArns.toString(); this.targetGroupName = this.resource.attrTargetGroupName; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 515ed687be3c7..5062453f2ea8a 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -93,8 +93,10 @@ "exclude": [ "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.BaseListener..params[2]", "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.BaseLoadBalancer.", - "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.TargetGroupBase." + "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.TargetGroupBase.", + "props-physical-name:@aws-cdk/aws-elasticloadbalancingv2.ApplicationListenerProps", + "props-physical-name:@aws-cdk/aws-elasticloadbalancingv2.NetworkListenerProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 5e77a74014285..86a5bda1fe5c3 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -292,7 +292,7 @@ export = { port: 80, targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)] }); - group.enableCookieStickiness(3600); + group.enableCookieStickiness(cdk.Duration.hours(1)); // THEN expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { @@ -329,8 +329,8 @@ export = { }); group.configureHealthCheck({ unhealthyThresholdCount: 3, - timeoutSeconds: 3600, - intervalSecs: 30, + timeout: cdk.Duration.hours(1), + interval: cdk.Duration.seconds(30), path: '/test', }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts index 98b536ffd7045..7f661f4d86345 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -2,7 +2,7 @@ import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import elbv2 = require('../../lib'); @@ -84,7 +84,7 @@ export = { vpc, deletionProtection: true, http2Enabled: false, - idleTimeoutSecs: 1000, + idleTimeout: cdk.Duration.seconds(1000), }); // THEN @@ -110,7 +110,7 @@ export = { 'Access logging'(test: Test) { // GIVEN - const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' }}); + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' } }); const vpc = new ec2.Vpc(stack, 'Stack'); const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); @@ -140,10 +140,13 @@ export = { Version: '2012-10-17', Statement: [ { - Action: [ "s3:PutObject*", "s3:Abort*" ], + Action: ["s3:PutObject*", "s3:Abort*"], Effect: 'Allow', - Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root" ] ] } }, - Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/*" ] ] } + Principal: { AWS: { "Fn::Join": ["", ["arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root"]] } }, + Resource: { + "Fn::Join": ["", [{ "Fn::GetAtt": ["AccessLoggingBucketA6D88F29", "Arn"] }, "/AWSLogs/", + { Ref: "AWS::AccountId" }, "/*"]] + } } ] } @@ -151,7 +154,7 @@ export = { // verify the ALB depends on the bucket *and* the bucket policy expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { - DependsOn: [ 'AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29' ] + DependsOn: ['AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29'] }, ResourcePart.CompleteDefinition)); test.done(); @@ -159,7 +162,7 @@ export = { 'access logging with prefix'(test: Test) { // GIVEN - const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' }}); + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' } }); const vpc = new ec2.Vpc(stack, 'Stack'); const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); @@ -192,10 +195,13 @@ export = { Version: '2012-10-17', Statement: [ { - Action: [ "s3:PutObject*", "s3:Abort*" ], + Action: ["s3:PutObject*", "s3:Abort*"], Effect: 'Allow', - Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root" ] ] } }, - Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/prefix-of-access-logs*" ] ] } + Principal: { AWS: { "Fn::Join": ["", ["arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root"]] } }, + Resource: { + "Fn::Join": ["", [{ "Fn::GetAtt": ["AccessLoggingBucketA6D88F29", "Arn"] }, "/prefix-of-access-logs/AWSLogs/", + { Ref: "AWS::AccountId" }, "/*"]] + } } ] } @@ -252,7 +258,7 @@ export = { // WHEN new elbv2.ApplicationLoadBalancer(stack, 'ALB', { - loadBalancerName: 'myLoadBalancer', + loadBalancerName: PhysicalName.of('myLoadBalancer'), vpc }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts index ddb45ba047259..034b79b2d6065 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.nlb.ts @@ -24,7 +24,7 @@ const group = listener.addTargets('Target', { targets: [new elbv2.IpTarget('10.0.1.1')] }); -group.node.addDependency(...vpc.internetDependencies); +group.node.addDependency(vpc.internetConnectivityEstablished); // The target's security group must allow being routed by the LB and the clients. diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts index 854b06801f6dc..744ccbd62c910 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.listener.ts @@ -99,8 +99,8 @@ export = { targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)] }); group.configureHealthCheck({ - timeoutSeconds: 3600, - intervalSecs: 30, + timeout: cdk.Duration.hours(1), + interval: cdk.Duration.seconds(30), path: '/test', }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts index b04d7198b98ce..855ddf338e26d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/nlb/test.load-balancer.ts @@ -81,7 +81,7 @@ export = { // WHEN new elbv2.NetworkLoadBalancer(stack, 'ALB', { - loadBalancerName: 'myLoadBalancer', + loadBalancerName: cdk.PhysicalName.of('myLoadBalancer'), vpc }); diff --git a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts index 62fd51bb8f7cc..6b6d706b676a0 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/ecs-task.ts @@ -115,8 +115,8 @@ export class EcsTask implements events.IRuleTarget { // Use a custom resource to "enhance" the target with network configuration // when using awsvpc network mode. if (this.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) { - const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.Private }; - const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.Private ? 'DISABLED' : 'ENABLED'; + const subnetSelection = this.props.subnetSelection || { subnetType: ec2.SubnetType.PRIVATE }; + const assignPublicIp = subnetSelection.subnetType === ec2.SubnetType.PRIVATE ? 'DISABLED' : 'ENABLED'; new cloudformation.AwsCustomResource(this.taskDefinition, 'PutTargets', { // `onCreate´ defaults to `onUpdate` and we don't need an `onDelete` here diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index 924e12491e703..e0f79b8d845ce 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -327,7 +327,7 @@ "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue" }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts index a8e6f35f86dcb..62d67211aada2 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts @@ -12,7 +12,9 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codebuild-events'); -const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'aws-cdk-codebuild-events' }); +const repo = new codecommit.Repository(stack, 'MyRepo', { + repositoryName: 'aws-cdk-codebuild-events', +}); const project = new codebuild.Project(stack, 'MyProject', { source: codebuild.Source.codeCommit({ repository: repo }), }); diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/integ.pipeline-event-target.ts b/packages/@aws-cdk/aws-events-targets/test/codepipeline/integ.pipeline-event-target.ts index 32d81c33dc046..24d9059395147 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/integ.pipeline-event-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/integ.pipeline-event-target.ts @@ -14,7 +14,7 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'pipeline-events'); const repo = new codecommit.Repository(stack, 'Repo', { - repositoryName: 'TestRepository' + repositoryName: 'TestRepository', }); const pipeline = new codepipeline.Pipeline(stack, 'pipelinePipeline22F2A91D'); @@ -24,7 +24,7 @@ pipeline.addStage({ stageName: 'Source', actions: [new MockAction({ actionName: 'CodeCommit', - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'CodeCommit', artifactBounds: { minInputs: 0, maxInputs: 0 , minOutputs: 1, maxOutputs: 1, }, configuration: { @@ -37,7 +37,7 @@ pipeline.addStage({ stageName: 'Build', actions: [new MockAction({ actionName: 'Hello', - category: codepipeline.ActionCategory.Approval, + category: codepipeline.ActionCategory.APPROVAL, provider: 'Manual', artifactBounds: { minInputs: 0, maxInputs: 0 , minOutputs: 0, maxOutputs: 0, }})] }); diff --git a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts index 52126d994a8fe..639f7d1ad3e1a 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codepipeline/pipeline.test.ts @@ -15,7 +15,7 @@ test('use codebuild project as an eventrule target', () => { stageName: 'Source', actions: [new TestAction({ actionName: 'Hello', - category: codepipeline.ActionCategory.Source, + category: codepipeline.ActionCategory.SOURCE, provider: 'x', artifactBounds: { minInputs: 0, maxInputs: 0 , minOutputs: 1, maxOutputs: 1, }, outputs: [srcArtifact]})] @@ -24,7 +24,7 @@ test('use codebuild project as an eventrule target', () => { stageName: 'Build', actions: [new TestAction({ actionName: 'Hello', - category: codepipeline.ActionCategory.Build, + category: codepipeline.ActionCategory.BUILD, provider: 'y', inputs: [srcArtifact], outputs: [buildArtifact], diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json index 82973ff075d5f..c1643598f14ae 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.expected.json @@ -506,7 +506,7 @@ "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" ] }, - "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionA8966A35": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -1197,4 +1197,4 @@ "Description": "Artifact hash for asset \"aws-ecs-integ-ecs/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.ts index 98245c87cc115..542a63e2af2f2 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-ec2-task.lit.ts @@ -30,7 +30,7 @@ class EventStack extends cdk.Stack { // An Rule that describes the event trigger (in this case a scheduled run) const rule = new events.Rule(this, 'Rule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); // Use EcsTask as the target of the Rule diff --git a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.ts b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.ts index e00c0bb5cd6d5..f807679e77dac 100644 --- a/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.ts +++ b/packages/@aws-cdk/aws-events-targets/test/ecs/integ.event-fargate-task.ts @@ -26,7 +26,7 @@ class EventStack extends cdk.Stack { // A rule that describes the event trigger (in this case a scheduled run) const rule = new events.Rule(this, 'Rule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); // Use EcsTask as the target of the Rule diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts index 44c0e12893f54..bf4e5f2f01877 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts @@ -14,12 +14,12 @@ const fn = new lambda.Function(stack, 'MyFunc', { }); const timer = new events.Rule(stack, 'Timer', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); timer.addTarget(new targets.LambdaFunction(fn)); const timer2 = new events.Rule(stack, 'Timer2', { - schedule: events.Schedule.rate(2, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(2)), }); timer2.addTarget(new targets.LambdaFunction(fn)); diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index 3f1dec6280d5f..f3a49b1a2f50a 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -9,10 +9,10 @@ test('use lambda as an event rule target', () => { const stack = new cdk.Stack(); const fn = newTestLambda(stack); const rule1 = new events.Rule(stack, 'Rule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); const rule2 = new events.Rule(stack, 'Rule2', { - schedule: events.Schedule.rate(5, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(5)), }); // WHEN diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json index 2c8279ffca762..a6236922383a9 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.expected.json @@ -57,7 +57,7 @@ "MyQueueE6CA6235": { "Type": "AWS::SQS::Queue" }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", @@ -108,4 +108,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts index 244d7d9aec39d..01e1ad0929812 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sns/integ.sns-event-rule-target.ts @@ -16,7 +16,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-sns-event-target'); const topic = new sns.Topic(stack, 'MyTopic'); const event = new events.Rule(stack, 'EveryMinute', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); const queue = new sqs.Queue(stack, 'MyQueue'); diff --git a/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts b/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts index 389cbe801b559..7fb36ff8d417c 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sns/sns.test.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import events = require('@aws-cdk/aws-events'); import sns = require('@aws-cdk/aws-sns'); -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import targets = require('../../lib'); test('sns topic as an event rule target', () => { @@ -9,7 +9,7 @@ test('sns topic as an event rule target', () => { const stack = new Stack(); const topic = new sns.Topic(stack, 'MyTopic'); const rule = new events.Rule(stack, 'MyRule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Hour), + schedule: events.Schedule.rate(Duration.hours(1)), }); // WHEN @@ -52,7 +52,7 @@ test('multiple uses of a topic as a target results in a single policy statement' // WHEN for (let i = 0; i < 5; ++i) { const rule = new events.Rule(stack, `Rule${i}`, { - schedule: events.Schedule.rate(1, events.TimeUnit.Hour), + schedule: events.Schedule.rate(Duration.hours(1)), }); rule.addTarget(new targets.SnsTopic(topic)); } diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts index 6a0f14cd63a10..a468df4954893 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/integ.sqs-event-rule-target.ts @@ -13,7 +13,7 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-sqs-event-target'); const event = new events.Rule(stack, 'MyRule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); const queue = new sqs.Queue(stack, 'MyQueue'); diff --git a/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts b/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts index 388cf21e60af9..1bc25c0163727 100644 --- a/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/sqs/sqs.test.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import events = require('@aws-cdk/aws-events'); import sqs = require('@aws-cdk/aws-sqs'); -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import targets = require('../../lib'); test('sns topic as an event rule target', () => { @@ -9,7 +9,7 @@ test('sns topic as an event rule target', () => { const stack = new Stack(); const queue = new sqs.Queue(stack, 'MyQueue'); const rule = new events.Rule(stack, 'MyRule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Hour), + schedule: events.Schedule.rate(Duration.hours(1)), }); // WHEN @@ -75,7 +75,7 @@ test('multiple uses of a queue as a target results in multi policy statement bec // WHEN for (let i = 0; i < 2; ++i) { const rule = new events.Rule(stack, `Rule${i}`, { - schedule: events.Schedule.rate(1, events.TimeUnit.Hour), + schedule: events.Schedule.rate(Duration.hours(1)), }); rule.addTarget(new targets.SqsQueue(queue)); } diff --git a/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts b/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts index 902efb7ee5ed3..f96132663acdc 100644 --- a/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/stepfunctions/statemachine.test.ts @@ -8,10 +8,10 @@ test('State machine can be used as Event Rule target', () => { // GIVEN const stack = new cdk.Stack(); const rule = new events.Rule(stack, 'Rule', { - schedule: events.Schedule.rate(1, events.TimeUnit.Minute), + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), }); const stateMachine = new sfn.StateMachine(stack, 'SM', { - definition: new sfn.Wait(stack, 'Hello', { duration: sfn.WaitDuration.seconds(10) }) + definition: new sfn.Wait(stack, 'Hello', { time: sfn.WaitTime.duration(cdk.Duration.seconds(10)) }) }); // WHEN diff --git a/packages/@aws-cdk/aws-events/lib/on-event-options.ts b/packages/@aws-cdk/aws-events/lib/on-event-options.ts index ff54919fcc8ba..b96726478a4ee 100644 --- a/packages/@aws-cdk/aws-events/lib/on-event-options.ts +++ b/packages/@aws-cdk/aws-events/lib/on-event-options.ts @@ -1,3 +1,4 @@ +import { PhysicalName } from "@aws-cdk/cdk"; import { EventPattern } from "./event-pattern"; import { IRuleTarget } from "./target"; @@ -24,7 +25,7 @@ export interface OnEventOptions { * * @default AWS CloudFormation generates a unique physical ID. */ - readonly ruleName?: string; + readonly ruleName?: PhysicalName; /** * Additional restrictions for the event to route to the specified target diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 8e2b5ef7c6665..9f05244f37693 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,4 +1,4 @@ -import { Construct, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { EventPattern } from './event-pattern'; import { CfnRule } from './events.generated'; import { IRule } from './rule-ref'; @@ -20,7 +20,7 @@ export interface RuleProps { * @default - AWS CloudFormation generates a unique physical ID and uses that ID * for the rule name. For more information, see Name Type. */ - readonly ruleName?: string; + readonly ruleName?: PhysicalName; /** * Indicates whether the rule is enabled. @@ -91,10 +91,12 @@ export class Rule extends Resource implements IRule { private scheduleExpression?: string; constructor(scope: Construct, id: string, props: RuleProps = { }) { - super(scope, id); + super(scope, id, { + physicalName: props.ruleName, + }); const resource = new CfnRule(this, 'Resource', { - name: props.ruleName, + name: this.physicalName, description: props.description, state: props.enabled == null ? 'ENABLED' : (props.enabled ? 'ENABLED' : 'DISABLED'), scheduleExpression: Lazy.stringValue({ produce: () => this.scheduleExpression }), @@ -102,7 +104,16 @@ export class Rule extends Resource implements IRule { targets: Lazy.anyValue({ produce: () => this.renderTargets() }), }); - this.ruleArn = resource.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.attrArn, + name: resource.name || '', + arnComponents: { + service: 'events', + resource: 'rule', + resourceName: this.physicalName, + }, + }); + this.ruleArn = resourceIdentifiers.arn; this.addEventPattern(props.eventPattern); this.scheduleExpression = props.schedule && props.schedule.expressionString; diff --git a/packages/@aws-cdk/aws-events/lib/schedule.ts b/packages/@aws-cdk/aws-events/lib/schedule.ts index 766c3a0bd91ec..dc55d776f1989 100644 --- a/packages/@aws-cdk/aws-events/lib/schedule.ts +++ b/packages/@aws-cdk/aws-events/lib/schedule.ts @@ -1,3 +1,5 @@ +import { Duration } from "@aws-cdk/cdk"; + /** * Schedule for scheduled event rules */ @@ -14,10 +16,15 @@ export abstract class Schedule { /** * Construct a schedule from an interval and a time unit */ - public static rate(interval: number, unit: TimeUnit): Schedule { - const unitStr = interval !== 1 ? `${unit}s` : unit; + public static rate(duration: Duration): Schedule { + if (duration.toSeconds() === 0) { + throw new Error('Duration cannot be 0'); + } - return new LiteralSchedule(`rate(${interval} ${unitStr})`); + let rate = maybeRate(duration.toDays({ integral: false }), 'day'); + if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); } + if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); } + return new LiteralSchedule(rate); } /** @@ -129,4 +136,19 @@ class LiteralSchedule extends Schedule { function fallback(x: T | undefined, def: T): T { return x === undefined ? def : x; +} + +/** + * Return the rate if the rate is whole number + */ +function maybeRate(interval: number, singular: string) { + if (interval === 0 || !Number.isInteger(interval)) { return undefined; } + return makeRate(interval, singular); +} + +/** + * Return 'rate(${interval} ${singular}(s))` for the interval + */ +function makeRate(interval: number, singular: string) { + return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/test/test.input.ts b/packages/@aws-cdk/aws-events/test/test.input.ts index e5034d6e6fe47..5a18d98ff6027 100644 --- a/packages/@aws-cdk/aws-events/test/test.input.ts +++ b/packages/@aws-cdk/aws-events/test/test.input.ts @@ -1,8 +1,8 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { IRuleTarget, RuleTargetInput, Schedule, TimeUnit } from '../lib'; +import { IRuleTarget, RuleTargetInput, Schedule } from '../lib'; import { Rule } from '../lib/rule'; export = { @@ -11,7 +11,7 @@ export = { // GIVEN const stack = new Stack(); const rule = new Rule(stack, 'Rule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(Duration.minutes(1)), }); // WHEN @@ -34,7 +34,7 @@ export = { // GIVEN const stack = new Stack(); const rule = new Rule(stack, 'Rule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(Duration.minutes(1)), }); // WHEN @@ -56,7 +56,7 @@ export = { // GIVEN const stack = new Stack(); const rule = new Rule(stack, 'Rule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(Duration.minutes(1)), }); // WHEN @@ -78,7 +78,7 @@ export = { // GIVEN const stack = new Stack(); const rule = new Rule(stack, 'Rule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(Duration.minutes(1)), }); const world = cdk.Lazy.stringValue({ produce: () => 'world' }); diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index 184ad3cb64479..70a4eaff37c43 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -4,7 +4,7 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import cdk = require('@aws-cdk/cdk'); import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { EventField, IRule, IRuleTarget, RuleTargetInput, Schedule, TimeUnit } from '../lib'; +import { EventField, IRule, IRuleTarget, RuleTargetInput, Schedule } from '../lib'; import { Rule } from '../lib/rule'; // tslint:disable:object-literal-key-quotes @@ -14,7 +14,7 @@ export = { const stack = new cdk.Stack(); new Rule(stack, 'MyRule', { - schedule: Schedule.rate(10, TimeUnit.Minute), + schedule: Schedule.rate(cdk.Duration.minutes(10)), }); expect(stack).toMatch({ @@ -37,8 +37,8 @@ export = { // WHEN new Rule(stack, 'MyRule', { - ruleName: 'PhysicalName', - schedule: Schedule.rate(10, TimeUnit.Minute), + ruleName: cdk.PhysicalName.of('PhysicalName'), + schedule: Schedule.rate(cdk.Duration.minutes(10)), }); // THEN @@ -174,7 +174,7 @@ export = { const rule = new Rule(stack, 'EventRule', { targets: [ t1 ], - schedule: Schedule.rate(5, TimeUnit.Minute), + schedule: Schedule.rate(cdk.Duration.minutes(5)), }); rule.addTarget(t2); @@ -216,7 +216,7 @@ export = { const stack = new cdk.Stack(); const rule = new Rule(stack, 'EventRule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(cdk.Duration.minutes(1)), }); // a plain string should just be stringified (i.e. double quotes added and escaped) @@ -301,7 +301,7 @@ export = { const stack = new cdk.Stack(); const rule = new Rule(stack, 'EventRule', { - schedule: Schedule.rate(1, TimeUnit.Minute), + schedule: Schedule.rate(cdk.Duration.minutes(1)), }); const role = new iam.Role(stack, 'SomeRole', { diff --git a/packages/@aws-cdk/aws-events/test/test.schedule.ts b/packages/@aws-cdk/aws-events/test/test.schedule.ts index bb3bfabfde834..8297560278c14 100644 --- a/packages/@aws-cdk/aws-events/test/test.schedule.ts +++ b/packages/@aws-cdk/aws-events/test/test.schedule.ts @@ -1,3 +1,4 @@ +import { Duration } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import events = require('../lib'); @@ -29,4 +30,18 @@ export = { }).expressionString); test.done(); }, + + 'rate must be whole number of minutes'(test: Test) { + test.throws(() => { + events.Schedule.rate(Duration.seconds(12345)); + }, /'12345 seconds' cannot be converted into a whole number of minutes/); + test.done(); + }, + + 'rate cannot be 0'(test: Test) { + test.throws(() => { + events.Schedule.rate(Duration.days(0)); + }, /Duration cannot be 0/); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index 2773a9ba271ca..4bfe841687a08 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -1,5 +1,5 @@ import s3 = require('@aws-cdk/aws-s3'); -import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnDatabase } from './glue.generated'; export interface IDatabase extends IResource { @@ -86,7 +86,9 @@ export class Database extends Resource implements IDatabase { public readonly locationUri: string; constructor(scope: Construct, id: string, props: DatabaseProps) { - super(scope, id); + super(scope, id, { + physicalName: PhysicalName.of(props.databaseName), + }); if (props.locationUri) { this.locationUri = props.locationUri; @@ -99,18 +101,28 @@ export class Database extends Resource implements IDatabase { const resource = new CfnDatabase(this, 'Resource', { catalogId: this.catalogId, databaseInput: { - name: props.databaseName, + name: this.physicalName, locationUri: this.locationUri } }); // see https://docs.aws.amazon.com/glue/latest/dg/glue-specifying-resource-arns.html#data-catalog-resource-arns - this.databaseName = resource.refAsString; - this.databaseArn = Stack.of(this).formatArn({ - service: 'glue', - resource: 'database', - resourceName: this.databaseName + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: Stack.of(this).formatArn({ + service: 'glue', + resource: 'database', + resourceName: resource.ref, + }), + name: resource.ref, + arnComponents: { + service: 'glue', + resource: 'database', + resourceName: this.physicalName, + }, }); + this.databaseName = resourceIdentifiers.name; + this.databaseArn = resourceIdentifiers.arn; + // catalogId is implicitly the accountId, which is why we don't pass the catalogId here this.catalogArn = Stack.of(this).formatArn({ service: 'glue', diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 77e261f980eb9..19a414f8e7dd4 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, Fn, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, Fn, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { DataFormat } from './data-format'; import { IDatabase } from './database'; import { CfnTable } from './glue.generated'; @@ -25,33 +25,33 @@ export interface ITable extends IResource { * @see https://docs.aws.amazon.com/athena/latest/ug/encryption.html */ export enum TableEncryption { - Unencrypted = 'Unencrypted', + UNENCRYPTED = 'Unencrypted', /** * Server side encryption (SSE) with an Amazon S3-managed key. * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html */ - S3Managed = 'SSE-S3', + S3_MANAGED = 'SSE-S3', /** * Server-side encryption (SSE) with an AWS KMS key managed by the account owner. * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html */ - Kms = 'SSE-KMS', + KMS = 'SSE-KMS', /** * Server-side encryption (SSE) with an AWS KMS key managed by the KMS service. */ - KmsManaged = 'SSE-KMS-MANAGED', + KMS_MANAGED = 'SSE-KMS-MANAGED', /** * Client-side encryption (CSE) with an AWS KMS key managed by the account owner. * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingClientSideEncryption.html */ - ClientSideKms = 'CSE-KMS' + CLIENT_SIDE_KMS = 'CSE-KMS' } export interface TableAttributes { @@ -230,7 +230,9 @@ export class Table extends Resource implements ITable { public readonly partitionKeys?: Column[]; constructor(scope: Construct, id: string, props: TableProps) { - super(scope, id); + super(scope, id, { + physicalName: PhysicalName.of(props.tableName), + }); this.database = props.database; this.dataFormat = props.dataFormat; @@ -252,13 +254,13 @@ export class Table extends Resource implements ITable { databaseName: props.database.databaseName, tableInput: { - name: props.tableName, + name: this.physicalName, description: props.description || `${props.tableName} generated by CDK`, partitionKeys: renderColumns(props.partitionKeys), parameters: { - has_encrypted_data: this.encryption !== TableEncryption.Unencrypted + has_encrypted_data: this.encryption !== TableEncryption.UNENCRYPTED }, storageDescriptor: { location: `s3://${this.bucket.bucketName}/${this.s3Prefix}`, @@ -276,12 +278,21 @@ export class Table extends Resource implements ITable { } }); - this.tableName = tableResource.refAsString; - this.tableArn = Stack.of(this).formatArn({ - service: 'glue', - resource: 'table', - resourceName: `${this.database.databaseName}/${this.tableName}` + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: Stack.of(this).formatArn({ + service: 'glue', + resource: 'table', + resourceName: `${this.database.databaseName}/${tableResource.ref}` + }), + name: tableResource.ref, + arnComponents: { + service: 'glue', + resource: 'table', + resourceName: `${this.database.databaseName}/${this.physicalName}` + }, }); + this.tableName = resourceIdentifiers.name; + this.tableArn = resourceIdentifiers.arn; } /** @@ -291,7 +302,7 @@ export class Table extends Resource implements ITable { */ public grantRead(grantee: iam.IGrantable): iam.Grant { const ret = this.grant(grantee, readPermissions); - if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantDecrypt(grantee); } + if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantDecrypt(grantee); } this.bucket.grantRead(grantee, this.s3Prefix); return ret; } @@ -303,7 +314,7 @@ export class Table extends Resource implements ITable { */ public grantWrite(grantee: iam.IGrantable): iam.Grant { const ret = this.grant(grantee, writePermissions); - if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncrypt(grantee); } + if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncrypt(grantee); } this.bucket.grantWrite(grantee, this.s3Prefix); return ret; } @@ -315,7 +326,7 @@ export class Table extends Resource implements ITable { */ public grantReadWrite(grantee: iam.IGrantable): iam.Grant { const ret = this.grant(grantee, [...readPermissions, ...writePermissions]); - if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncryptDecrypt(grantee); } + if (this.encryptionKey && this.encryption === TableEncryption.CLIENT_SIDE_KMS) { this.encryptionKey.grantEncryptDecrypt(grantee); } this.bucket.grantReadWrite(grantee, this.s3Prefix); return ret; } @@ -345,24 +356,24 @@ function validateSchema(columns: Column[], partitionKeys?: Column[]): void { // map TableEncryption to bucket's SSE configuration (s3.BucketEncryption) const encryptionMappings = { - [TableEncryption.S3Managed]: s3.BucketEncryption.S3Managed, - [TableEncryption.KmsManaged]: s3.BucketEncryption.KmsManaged, - [TableEncryption.Kms]: s3.BucketEncryption.Kms, - [TableEncryption.ClientSideKms]: s3.BucketEncryption.Unencrypted, - [TableEncryption.Unencrypted]: s3.BucketEncryption.Unencrypted, + [TableEncryption.S3_MANAGED]: s3.BucketEncryption.S3_MANAGED, + [TableEncryption.KMS_MANAGED]: s3.BucketEncryption.KMS_MANAGED, + [TableEncryption.KMS]: s3.BucketEncryption.KMS, + [TableEncryption.CLIENT_SIDE_KMS]: s3.BucketEncryption.UNENCRYPTED, + [TableEncryption.UNENCRYPTED]: s3.BucketEncryption.UNENCRYPTED, }; // create the bucket to store a table's data depending on the `encryption` and `encryptionKey` properties. function createBucket(table: Table, props: TableProps) { - const encryption = props.encryption || TableEncryption.Unencrypted; + const encryption = props.encryption || TableEncryption.UNENCRYPTED; let bucket = props.bucket; - if (bucket && (encryption !== TableEncryption.Unencrypted && encryption !== TableEncryption.ClientSideKms)) { + if (bucket && (encryption !== TableEncryption.UNENCRYPTED && encryption !== TableEncryption.CLIENT_SIDE_KMS)) { throw new Error('you can not specify encryption settings if you also provide a bucket'); } let encryptionKey: kms.IKey | undefined; - if (encryption === TableEncryption.ClientSideKms && props.encryptionKey === undefined) { + if (encryption === TableEncryption.CLIENT_SIDE_KMS && props.encryptionKey === undefined) { // CSE-KMS should behave the same as SSE-KMS - use the provided key or create one automatically // Since Bucket only knows about SSE, we repeat the logic for CSE-KMS at the Table level. encryptionKey = new kms.Key(table, 'Key'); @@ -372,7 +383,7 @@ function createBucket(table: Table, props: TableProps) { // create the bucket if none was provided if (!bucket) { - if (encryption === TableEncryption.ClientSideKms) { + if (encryption === TableEncryption.CLIENT_SIDE_KMS) { bucket = new s3.Bucket(table, 'Bucket'); } else { bucket = new s3.Bucket(table, 'Bucket', { diff --git a/packages/@aws-cdk/aws-glue/test/integ.table.ts b/packages/@aws-cdk/aws-glue/test/integ.table.ts index c624aa8ec75cd..4dd92193d01fa 100644 --- a/packages/@aws-cdk/aws-glue/test/integ.table.ts +++ b/packages/@aws-cdk/aws-glue/test/integ.table.ts @@ -9,7 +9,7 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-glue'); const database = new glue.Database(stack, 'MyDatabase', { - databaseName: 'my_database' + databaseName: 'my_database', }); const ordinaryTable = new glue.Table(stack, 'MyTable', { @@ -70,7 +70,7 @@ const encryptedTable = new glue.Table(stack, 'MyEncryptedTable', { type: glue.Schema.smallint }], dataFormat: glue.DataFormat.Json, - encryption: glue.TableEncryption.Kms, + encryption: glue.TableEncryption.KMS, encryptionKey: new kms.Key(stack, 'MyKey') }); diff --git a/packages/@aws-cdk/aws-glue/test/test.database.ts b/packages/@aws-cdk/aws-glue/test/test.database.ts index 71501270ea99b..58fbc58869652 100644 --- a/packages/@aws-cdk/aws-glue/test/test.database.ts +++ b/packages/@aws-cdk/aws-glue/test/test.database.ts @@ -8,7 +8,7 @@ export = { const stack = new Stack(); new glue.Database(stack, 'Database', { - databaseName: 'test_database' + databaseName: 'test_database', }); expect(stack).toMatch({ diff --git a/packages/@aws-cdk/aws-glue/test/test.table.ts b/packages/@aws-cdk/aws-glue/test/test.table.ts index a2a73d94bb69c..0e2447fd5a2e7 100644 --- a/packages/@aws-cdk/aws-glue/test/test.table.ts +++ b/packages/@aws-cdk/aws-glue/test/test.table.ts @@ -11,7 +11,7 @@ export = { const app = new cdk.App(); const dbStack = new cdk.Stack(app, 'db'); const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const tableStack = new cdk.Stack(app, 'table'); @@ -24,7 +24,7 @@ export = { }], dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.Unencrypted); + test.equals(table.encryption, glue.TableEncryption.UNENCRYPTED); expect(tableStack).to(haveResource('AWS::S3::Bucket', { Type: "AWS::S3::Bucket", @@ -82,7 +82,7 @@ export = { const app = new cdk.App(); const dbStack = new cdk.Stack(app, 'db'); const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const tableStack = new cdk.Stack(app, 'table'); @@ -99,7 +99,7 @@ export = { }], dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.Unencrypted); + test.equals(table.encryption, glue.TableEncryption.UNENCRYPTED); test.equals(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -159,7 +159,7 @@ export = { 'compressed table'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -226,7 +226,7 @@ export = { 'SSE-S3'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -236,10 +236,10 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.S3Managed, + encryption: glue.TableEncryption.S3_MANAGED, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.S3Managed); + test.equals(table.encryption, glue.TableEncryption.S3_MANAGED); test.equals(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -305,7 +305,7 @@ export = { 'SSE-KMS (implicitly created key)'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -315,10 +315,10 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.Kms, + encryption: glue.TableEncryption.KMS, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.Kms); + test.equals(table.encryption, glue.TableEncryption.KMS); test.equals(table.encryptionKey, table.bucket.encryptionKey); expect(stack).to(haveResource('AWS::KMS::Key', { @@ -434,7 +434,7 @@ export = { 'SSE-KMS (explicitly created key)'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const encryptionKey = new kms.Key(stack, 'MyKey'); @@ -445,11 +445,11 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.Kms, + encryption: glue.TableEncryption.KMS, encryptionKey, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.Kms); + test.equals(table.encryption, glue.TableEncryption.KMS); test.equals(table.encryptionKey, table.bucket.encryptionKey); test.notEqual(table.encryptionKey, undefined); @@ -565,7 +565,7 @@ export = { 'SSE-KMS_MANAGED'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -575,10 +575,10 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.KmsManaged, + encryption: glue.TableEncryption.KMS_MANAGED, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.KmsManaged); + test.equals(table.encryption, glue.TableEncryption.KMS_MANAGED); test.equals(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -644,7 +644,7 @@ export = { 'CSE-KMS (implicitly created key)'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -654,10 +654,10 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.ClientSideKms, + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.ClientSideKms); + test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); test.notEqual(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -755,7 +755,7 @@ export = { 'CSE-KMS (explicitly created key)'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const encryptionKey = new kms.Key(stack, 'MyKey'); @@ -766,11 +766,11 @@ export = { name: 'col', type: glue.Schema.string }], - encryption: glue.TableEncryption.ClientSideKms, + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, encryptionKey, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.ClientSideKms); + test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); test.notEqual(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -868,7 +868,7 @@ export = { 'CSE-KMS (explicitly passed bucket and key)'(test: Test) { const stack = new cdk.Stack(); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const bucket = new s3.Bucket(stack, 'Bucket'); const encryptionKey = new kms.Key(stack, 'MyKey'); @@ -881,11 +881,11 @@ export = { type: glue.Schema.string }], bucket, - encryption: glue.TableEncryption.ClientSideKms, + encryption: glue.TableEncryption.CLIENT_SIDE_KMS, encryptionKey, dataFormat: glue.DataFormat.Json, }); - test.equals(table.encryption, glue.TableEncryption.ClientSideKms); + test.equals(table.encryption, glue.TableEncryption.CLIENT_SIDE_KMS); test.notEqual(table.encryptionKey, undefined); test.equals(table.bucket.encryptionKey, undefined); @@ -987,7 +987,7 @@ export = { const stack = new cdk.Stack(app, 'app'); const bucket = new s3.Bucket(stack, 'ExplicitBucket'); const database = new glue.Database(dbStack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); new glue.Table(stack, 'Table', { @@ -1054,7 +1054,7 @@ export = { const stack = new cdk.Stack(); const user = new iam.User(stack, 'User'); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -1160,7 +1160,7 @@ export = { const stack = new cdk.Stack(); const user = new iam.User(stack, 'User'); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -1264,7 +1264,7 @@ export = { const stack = new cdk.Stack(); const user = new iam.User(stack, 'User'); const database = new glue.Database(stack, 'Database', { - databaseName: 'database' + databaseName: 'database', }); const table = new glue.Table(stack, 'Table', { @@ -1447,7 +1447,7 @@ export = { type: glue.Schema.string }], bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: glue.TableEncryption.Kms + encryption: glue.TableEncryption.KMS }), undefined, 'you can not specify encryption settings if you also provide a bucket'); test.done(); }, @@ -1486,7 +1486,7 @@ export = { type: glue.Schema.string }], bucket: new s3.Bucket(new cdk.Stack(), 'Bucket'), - encryption: glue.TableEncryption.ClientSideKms + encryption: glue.TableEncryption.CLIENT_SIDE_KMS })); test.done(); } @@ -1511,7 +1511,7 @@ function createTable(props: Pick this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }), path: props.path, }); - this.groupName = group.refAsString; - this.groupArn = group.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: group.attrArn, + name: group.ref, + arnComponents: { + region: '', // IAM is global in each partition + service: 'iam', + resource: 'group', + resourceName: this.physicalName, + }, + }); + this.groupName = resourceIdentifiers.name; + this.groupArn = resourceIdentifiers.arn; } /** diff --git a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts index 376d3c37fa6e5..ec3f58e5d8864 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-statement.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-statement.ts @@ -19,7 +19,7 @@ export class PolicyStatement { private condition: { [key: string]: any } = { }; constructor(props: PolicyStatementProps = {}) { - this.effect = Effect.Allow; + this.effect = Effect.ALLOW; this.addActions(...props.actions || []); this.addPrincipals(...props.principals || []); @@ -214,8 +214,8 @@ export class PolicyStatement { } export enum Effect { - Allow = 'Allow', - Deny = 'Deny', + ALLOW = 'Allow', + DENY = 'Deny', } /** diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index e5ef29ec99ff4..73a5b39dcf671 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { IGroup } from './group'; import { CfnPolicy } from './iam.generated'; import { PolicyDocument } from './policy-document'; @@ -23,7 +23,7 @@ export interface PolicyProps { * @default - Uses the logical ID of the policy resource, which is ensured * to be unique within the stack. */ - readonly policyName?: string; + readonly policyName?: PhysicalName; /** * Users to attach this policy to. @@ -91,20 +91,23 @@ export class Policy extends Resource implements IPolicy { private readonly groups = new Array(); constructor(scope: Construct, id: string, props: PolicyProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.policyName || + // generatePolicyName will take the last 128 characters of the logical id since + // policy names are limited to 128. the last 8 chars are a stack-unique hash, so + // that shouod be sufficient to ensure uniqueness within a principal. + PhysicalName.of(Lazy.stringValue({ produce: () => generatePolicyName(resource.logicalId) }).toString()), + }); const resource = new CfnPolicy(this, 'Resource', { policyDocument: this.document, - policyName: Lazy.stringValue({ produce: () => this.policyName }).toString(), + policyName: this.physicalName, // guaranteed to be not null because of above `super` call roles: undefinedIfEmpty(() => this.roles.map(r => r.roleName)), users: undefinedIfEmpty(() => this.users.map(u => u.userName)), groups: undefinedIfEmpty(() => this.groups.map(g => g.groupName)), }); - // generatePolicyName will take the last 128 characters of the logical id since - // policy names are limited to 128. the last 8 chars are a stack-unique hash, so - // that shouod be sufficient to ensure uniqueness within a principal. - this.policyName = props.policyName || generatePolicyName(resource.logicalId); + this.policyName = this.physicalName!; if (props.users) { props.users.forEach(u => this.attachToUser(u)); diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 0a51516ee4252..a6214289757ba 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,4 +1,4 @@ -import { Construct, Lazy, PhysicalName, Resource, ResourceIdentifiers, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { Grant } from './grant'; import { CfnRole } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -73,9 +73,8 @@ export interface RoleProps { readonly roleName?: PhysicalName; /** - * The maximum session duration (in seconds) that you want to set for the - * specified role. This setting can have a value from 1 hour (3600sec) to - * 12 (43200sec) hours. + * The maximum session duration that you want to set for the specified role. + * This setting can have a value from 1 hour (3600sec) to 12 (43200sec) hours. * * Anyone who assumes the role from the AWS CLI or API can use the * DurationSeconds API parameter or the duration-seconds CLI parameter to @@ -90,9 +89,9 @@ export interface RoleProps { * * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html * - * @default 3600 (1 hour) + * @default Duration.hours(1) */ - readonly maxSessionDurationSec?: number; + readonly maxSessionDuration?: Duration; } /** @@ -207,26 +206,27 @@ export class Role extends Resource implements IRole { this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, props.externalId); this.managedPolicies.push(...props.managedPolicies || []); - validateMaxSessionDuration(props.maxSessionDurationSec); + const maxSessionDuration = props.maxSessionDuration && props.maxSessionDuration.toSeconds(); + validateMaxSessionDuration(maxSessionDuration); const role = new CfnRole(this, 'Resource', { assumeRolePolicyDocument: this.assumeRolePolicy as any, managedPolicyArns: Lazy.listValue({ produce: () => this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }), policies: _flatten(props.inlinePolicies), path: props.path, - roleName: this.physicalName.value, - maxSessionDuration: props.maxSessionDurationSec, + roleName: this.physicalName, + maxSessionDuration, }); this.roleId = role.attrRoleId; - const resourceIdentifiers = new ResourceIdentifiers(this, { + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ arn: role.attrArn, - name: role.refAsString, + name: role.ref, arnComponents: { region: '', // IAM is global in each partition service: 'iam', resource: 'role', - resourceName: this.physicalName.value, + resourceName: this.physicalName, }, }); this.roleArn = resourceIdentifiers.arn; diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 7725b8269141d..f45828900871a 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,4 +1,4 @@ -import { Construct, Lazy, Resource, SecretValue } from '@aws-cdk/cdk'; +import { Construct, Lazy, PhysicalName, Resource, SecretValue } from '@aws-cdk/cdk'; import { IGroup } from './group'; import { CfnUser } from './iam.generated'; import { IIdentity } from './identity-base'; @@ -55,7 +55,7 @@ export interface UserProps { * * @default Generated by CloudFormation (recommended) */ - readonly userName?: string; + readonly userName?: PhysicalName; /** * The password for the user. This is required so the user can access the @@ -103,18 +103,31 @@ export class User extends Resource implements IIdentity { private defaultPolicy?: Policy; constructor(scope: Construct, id: string, props: UserProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.userName, + }); const user = new CfnUser(this, 'Resource', { - userName: props.userName, + userName: this.physicalName, groups: undefinedIfEmpty(() => this.groups), managedPolicyArns: Lazy.listValue({ produce: () => this.managedPolicies.map(p => p.managedPolicyArn) }, { omitEmpty: true }), path: props.path, loginProfile: this.parseLoginProfile(props) }); - this.userName = user.refAsString; - this.userArn = user.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: user.attrArn, + name: user.ref, + arnComponents: { + region: '', // IAM is global in each partition + service: 'iam', + resource: 'user', + resourceName: this.physicalName, + }, + }); + this.userName = resourceIdentifiers.name; + this.userArn = resourceIdentifiers.arn; + this.policyFragment = new ArnPrincipal(this.userArn).policyFragment; if (props.groups) { diff --git a/packages/@aws-cdk/aws-iam/test/integ.policy.ts b/packages/@aws-cdk/aws-iam/test/integ.policy.ts index 193643b3e166a..e25390daab79b 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.policy.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.policy.ts @@ -1,4 +1,4 @@ -import { App, Stack } from "@aws-cdk/cdk"; +import { App, PhysicalName, Stack } from "@aws-cdk/cdk"; import { Policy, PolicyStatement } from "../lib"; import { User } from "../lib/user"; @@ -8,7 +8,7 @@ const stack = new Stack(app, 'aws-cdk-iam-policy'); const user = new User(stack, 'MyUser'); -const policy = new Policy(stack, 'HelloPolicy', { policyName: 'Default' }); +const policy = new Policy(stack, 'HelloPolicy', { policyName: PhysicalName.of('Default') }); policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); policy.attachToUser(user); diff --git a/packages/@aws-cdk/aws-iam/test/integ.role.ts b/packages/@aws-cdk/aws-iam/test/integ.role.ts index b18b429db597f..73487ee8c8d5b 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.role.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.role.ts @@ -1,4 +1,4 @@ -import { App, Stack } from "@aws-cdk/cdk"; +import { App, PhysicalName, Stack } from "@aws-cdk/cdk"; import { AccountRootPrincipal, Policy, PolicyStatement, Role, ServicePrincipal } from "../lib"; const app = new App(); @@ -11,7 +11,7 @@ const role = new Role(stack, 'TestRole', { role.addToPolicy(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); -const policy = new Policy(stack, 'HelloPolicy', { policyName: 'Default' }); +const policy = new Policy(stack, 'HelloPolicy', { policyName: PhysicalName.of('Default') }); policy.addStatements(new PolicyStatement({ actions: ['ec2:*'], resources: ['*'] })); policy.attachToRole(role); diff --git a/packages/@aws-cdk/aws-iam/test/integ.user.ts b/packages/@aws-cdk/aws-iam/test/integ.user.ts index 6870ac52ea550..3498a9a1f7932 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.user.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.user.ts @@ -1,4 +1,4 @@ -import { App, SecretValue, Stack } from "@aws-cdk/cdk"; +import { App, PhysicalName, SecretValue, Stack } from "@aws-cdk/cdk"; import { User } from "../lib"; const app = new App(); @@ -6,7 +6,7 @@ const app = new App(); const stack = new Stack(app, 'aws-cdk-iam-user'); new User(stack, 'MyUser', { - userName: 'benisrae', + userName: PhysicalName.of('benisrae'), password: SecretValue.plainText('1234'), passwordResetRequired: true }); diff --git a/packages/@aws-cdk/aws-iam/test/test.escape-hatch.ts b/packages/@aws-cdk/aws-iam/test/test.escape-hatch.ts index 942dd55127ed8..908eb61611f3e 100644 --- a/packages/@aws-cdk/aws-iam/test/test.escape-hatch.ts +++ b/packages/@aws-cdk/aws-iam/test/test.escape-hatch.ts @@ -2,7 +2,7 @@ // because we want to verify them end-to-end, as a complement to the unit // tests in the @aws-cdk/cdk module import { expect } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import iam = require('../lib'); @@ -12,7 +12,7 @@ export = { 'addPropertyOverride should allow overriding supported properties'(test: Test) { const stack = new Stack(); const user = new iam.User(stack, 'user', { - userName: 'MyUserName' + userName: PhysicalName.of('MyUserName'), }); const cfn = user.node.findChild('Resource') as iam.CfnUser; @@ -33,7 +33,7 @@ export = { 'addPropertyOverrides should allow specifying arbitrary properties'(test: Test) { // GIVEN const stack = new Stack(); - const user = new iam.User(stack, 'user', { userName: 'MyUserName' }); + const user = new iam.User(stack, 'user', { userName: PhysicalName.of('MyUserName') }); const cfn = user.node.findChild('Resource') as iam.CfnUser; // WHEN @@ -59,7 +59,7 @@ export = { 'addOverride should allow overriding properties'(test: Test) { // GIVEN const stack = new Stack(); - const user = new iam.User(stack, 'user', { userName: 'MyUserName' }); + const user = new iam.User(stack, 'user', { userName: PhysicalName.of('MyUserName') }); const cfn = user.node.findChild('Resource') as iam.CfnUser; cfn.options.updatePolicy = { useOnlineResharding: true }; @@ -95,4 +95,4 @@ export = { test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts index d2f3965a9eeb3..7da37983c8221 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -45,7 +45,7 @@ export = { p1.addResources('*'); const p2 = new PolicyStatement(); - p2.effect = Effect.Deny; + p2.effect = Effect.DENY; p2.addActions('cloudformation:CreateStack'); doc.addStatements(p1); diff --git a/packages/@aws-cdk/aws-iam/test/test.policy.ts b/packages/@aws-cdk/aws-iam/test/test.policy.ts index fd2530c6ab835..7547e6c356e7d 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy.ts @@ -1,5 +1,5 @@ import { expect } from '@aws-cdk/assert'; -import { App, Stack } from '@aws-cdk/cdk'; +import { App, PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Group, Policy, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; import { generatePolicyName } from '../lib/util'; @@ -18,7 +18,7 @@ export = { const app = new App(); const stack = new Stack(app, 'MyStack'); - const policy = new Policy(stack, 'MyPolicy', { policyName: 'MyPolicyName' }); + const policy = new Policy(stack, 'MyPolicy', { policyName: PhysicalName.of('MyPolicyName') }); policy.addStatements(new PolicyStatement({ resources: ['*'], actions: ['sqs:SendMessage'] })); policy.addStatements(new PolicyStatement({ resources: ['arn'], actions: ['sns:Subscribe'] })); @@ -78,7 +78,7 @@ export = { }); new Policy(stack, 'MyTestPolicy', { - policyName: 'Foo', + policyName: PhysicalName.of('Foo'), users: [ user1 ], groups: [ group1 ], roles: [ role1 ], @@ -141,7 +141,7 @@ export = { const stack = new Stack(app, 'MyStack'); const p = new Policy(stack, 'MyTestPolicy', { - policyName: 'Foo', + policyName: PhysicalName.of('Foo'), }); p.attachToUser(new User(stack, 'User1')); @@ -222,8 +222,8 @@ export = { const stack = new Stack(app, 'MyStack'); // create two policies named Foo and attach them both to the same user/group/role - const p1 = new Policy(stack, 'P1', { policyName: 'Foo' }); - const p2 = new Policy(stack, 'P2', { policyName: 'Foo' }); + const p1 = new Policy(stack, 'P1', { policyName: PhysicalName.of('Foo') }); + const p2 = new Policy(stack, 'P2', { policyName: PhysicalName.of('Foo') }); const p3 = new Policy(stack, 'P3'); // uses logicalID as name const user = new User(stack, 'MyUser'); diff --git a/packages/@aws-cdk/aws-iam/test/test.role.ts b/packages/@aws-cdk/aws-iam/test/test.role.ts index 93d423b653200..0191a841825f0 100644 --- a/packages/@aws-cdk/aws-iam/test/test.role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.role.ts @@ -1,5 +1,5 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -194,7 +194,7 @@ export = { 'can be used to specify the maximum session duration for assuming the role'(test: Test) { const stack = new Stack(); - new Role(stack, 'MyRole', { maxSessionDurationSec: 3700, assumedBy: new ServicePrincipal('sns.amazonaws.com') }); + new Role(stack, 'MyRole', { maxSessionDuration: Duration.seconds(3700), assumedBy: new ServicePrincipal('sns.amazonaws.com') }); expect(stack).to(haveResource('AWS::IAM::Role', { MaxSessionDuration: 3700 @@ -208,13 +208,13 @@ export = { const assumedBy = new ServicePrincipal('bla'); - new Role(stack, 'MyRole1', { assumedBy, maxSessionDurationSec: 3600 }); - new Role(stack, 'MyRole2', { assumedBy, maxSessionDurationSec: 43200 }); + new Role(stack, 'MyRole1', { assumedBy, maxSessionDuration: Duration.hours(1) }); + new Role(stack, 'MyRole2', { assumedBy, maxSessionDuration: Duration.hours(12) }); const expected = (val: any) => `maxSessionDuration is set to ${val}, but must be >= 3600sec (1hr) and <= 43200sec (12hrs)`; - test.throws(() => new Role(stack, 'MyRole3', { assumedBy, maxSessionDurationSec: 60 }), expected(60)); - test.throws(() => new Role(stack, 'MyRole4', { assumedBy, maxSessionDurationSec: 3599 }), expected(3599)); - test.throws(() => new Role(stack, 'MyRole5', { assumedBy, maxSessionDurationSec: 43201 }), expected(43201)); + test.throws(() => new Role(stack, 'MyRole3', { assumedBy, maxSessionDuration: Duration.minutes(1) }), expected(60)); + test.throws(() => new Role(stack, 'MyRole4', { assumedBy, maxSessionDuration: Duration.seconds(3599) }), expected(3599)); + test.throws(() => new Role(stack, 'MyRole5', { assumedBy, maxSessionDuration: Duration.seconds(43201) }), expected(43201)); test.done(); } diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index eedde8e6c2e39..862c6bba6ecfd 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnStream } from './kinesis.generated'; export interface IStream extends IResource { @@ -173,7 +173,7 @@ export interface StreamProps { * Enforces a particular physical stream name. * @default */ - readonly streamName?: string; + readonly streamName?: PhysicalName; /** * The number of hours for the data records that are stored in shards to remain accessible. @@ -241,7 +241,9 @@ export class Stream extends StreamBase { private readonly stream: CfnStream; constructor(scope: Construct, id: string, props: StreamProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.streamName, + }); const shardCount = props.shardCount || 1; const retentionPeriodHours = props.retentionPeriodHours || 24; @@ -252,13 +254,24 @@ export class Stream extends StreamBase { const { streamEncryption, encryptionKey } = this.parseEncryption(props); this.stream = new CfnStream(this, "Resource", { - name: props.streamName, + name: this.physicalName, retentionPeriodHours, shardCount, streamEncryption }); - this.streamArn = this.stream.attrArn; - this.streamName = this.stream.refAsString; + + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: this.stream.attrArn, + name: this.stream.ref, + arnComponents: { + service: 'kinesis', + resource: 'stream', + resourceName: this.physicalName, + }, + }); + this.streamArn = resourceIdentifiers.arn; + this.streamName = resourceIdentifiers.name; + this.encryptionKey = encryptionKey; if (props.streamName) { this.node.addMetadata('aws:cdk:hasPhysicalName', props.streamName); } @@ -274,18 +287,18 @@ export class Stream extends StreamBase { } { // default to unencrypted. - const encryptionType = props.encryption || StreamEncryption.Unencrypted; + const encryptionType = props.encryption || StreamEncryption.UNENCRYPTED; // if encryption key is set, encryption must be set to KMS. - if (encryptionType !== StreamEncryption.Kms && props.encryptionKey) { + if (encryptionType !== StreamEncryption.KMS && props.encryptionKey) { throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType})`); } - if (encryptionType === StreamEncryption.Unencrypted) { + if (encryptionType === StreamEncryption.UNENCRYPTED) { return { streamEncryption: undefined, encryptionKey: undefined }; } - if (encryptionType === StreamEncryption.Kms) { + if (encryptionType === StreamEncryption.KMS) { const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', { description: `Created by ${this.node.path}` }); @@ -308,11 +321,11 @@ export enum StreamEncryption { /** * Records in the stream are not encrypted. */ - Unencrypted = 'NONE', + UNENCRYPTED = 'NONE', /** * Server-side encryption with a KMS key managed by the user. * If `encryptionKey` is specified, this key will be used, otherwise, one will be defined. */ - Kms = 'KMS', + KMS = 'KMS', } diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index 14d8261b86eeb..c784c27de1e02 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -107,7 +107,7 @@ export = { const stack = new Stack(); new Stream(stack, 'MyStream', { - encryption: StreamEncryption.Kms + encryption: StreamEncryption.KMS }); expect(stack).toMatch({ @@ -189,7 +189,7 @@ export = { }); new Stream(stack, 'MyStream', { - encryption: StreamEncryption.Kms, + encryption: StreamEncryption.KMS, encryptionKey: explicitKey }); @@ -269,7 +269,7 @@ export = { "grantRead creates and attaches a policy with read only access to Stream and EncryptionKey"(test: Test) { const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { - encryption: StreamEncryption.Kms + encryption: StreamEncryption.KMS }); const user = new iam.User(stack, "MyUser"); @@ -405,7 +405,7 @@ export = { "grantWrite creates and attaches a policy with write only access to Stream and EncryptionKey"(test: Test) { const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { - encryption: StreamEncryption.Kms + encryption: StreamEncryption.KMS }); const user = new iam.User(stack, "MyUser"); @@ -549,7 +549,7 @@ export = { "grantReadWrite creates and attaches a policy with access to Stream and EncryptionKey"(test: Test) { const stack = new Stack(); const stream = new Stream(stack, 'MyStream', { - encryption: StreamEncryption.Kms + encryption: StreamEncryption.KMS }); const user = new iam.User(stack, "MyUser"); @@ -938,7 +938,7 @@ export = { const app = new App(); const stackA = new Stack(app, 'stackA'); const streamFromStackA = new Stream(stackA, 'MyStream', { - encryption: StreamEncryption.Kms + encryption: StreamEncryption.KMS }); const stackB = new Stack(app, 'stackB'); diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index 963797f16cfd7..807db70f8b85b 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -42,15 +42,14 @@ pass the construct to the other stack: ### Importing existing keys To use a KMS key that is not defined in this CDK app, but is created through other means, use -`Key.import(parent, name, ref)`: +`Key.fromKeyArn(parent, name, ref)`: ```ts -const myKeyImported = Key.import(this, 'MyImportedKey', { - keyArn: 'arn:aws:...' -}); +import kms = require('@aws-cdk/aws-kms'); +const myKeyImported = kms.Key.fromKeyArn(this, 'MyImportedKey', 'arn:aws:...'); // you can do stuff with this imported key. -key.addAlias('alias/foo'); +myKeyImported.addAlias('alias/foo'); ``` Note that a call to `.addToPolicy(statement)` on `myKeyImported` will not have diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index 26a068caa4363..416c28f34ef68 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -33,7 +33,7 @@ export interface AliasProps { * forward slash, such as alias/. You can't specify aliases that begin with * alias/AWS. These aliases are reserved. */ - readonly name: string; + readonly aliasName: string; /** * The ID of the key for which you are creating the alias. Specify the key's @@ -81,26 +81,26 @@ export class Alias extends AliasBase { constructor(scope: Construct, id: string, props: AliasProps) { super(scope, id); - if (!Token.isUnresolved(props.name)) { - if (!props.name.startsWith(REQUIRED_ALIAS_PREFIX)) { - throw new Error(`Alias must start with the prefix "${REQUIRED_ALIAS_PREFIX}": ${props.name}`); + if (!Token.isUnresolved(props.aliasName)) { + if (!props.aliasName.startsWith(REQUIRED_ALIAS_PREFIX)) { + throw new Error(`Alias must start with the prefix "${REQUIRED_ALIAS_PREFIX}": ${props.aliasName}`); } - if (props.name === REQUIRED_ALIAS_PREFIX) { - throw new Error(`Alias must include a value after "${REQUIRED_ALIAS_PREFIX}": ${props.name}`); + if (props.aliasName === REQUIRED_ALIAS_PREFIX) { + throw new Error(`Alias must include a value after "${REQUIRED_ALIAS_PREFIX}": ${props.aliasName}`); } - if (props.name.startsWith(DISALLOWED_PREFIX)) { - throw new Error(`Alias cannot start with ${DISALLOWED_PREFIX}: ${props.name}`); + if (props.aliasName.startsWith(DISALLOWED_PREFIX)) { + throw new Error(`Alias cannot start with ${DISALLOWED_PREFIX}: ${props.aliasName}`); } - if (!props.name.match(/^[a-zA-Z0-9:/_-]{1,256}$/)) { + if (!props.aliasName.match(/^[a-zA-Z0-9:/_-]{1,256}$/)) { throw new Error(`Alias name must be between 1 and 256 characters in a-zA-Z0-9:/_-`); } } const resource = new CfnAlias(this, 'Resource', { - aliasName: props.name, + aliasName: props.aliasName, targetKeyId: props.targetKey.keyArn }); diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 94bb1ec8e438e..e69f997ad5695 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -68,7 +68,7 @@ abstract class KeyBase extends Resource implements IKey { * Defines a new alias for the key. */ public addAlias(alias: string): Alias { - return new Alias(this, 'Alias', { name: alias, targetKey: this }); + return new Alias(this, 'Alias', { aliasName: alias, targetKey: this }); } /** diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index 6d5efaa5cc6f6..2e9ec59da5169 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -81,5 +81,10 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-kms.KeyProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kms/test/test.alias.ts b/packages/@aws-cdk/aws-kms/test/test.alias.ts index c9549e319f940..c4d3d0219b840 100644 --- a/packages/@aws-cdk/aws-kms/test/test.alias.ts +++ b/packages/@aws-cdk/aws-kms/test/test.alias.ts @@ -10,7 +10,7 @@ export = { const stack = new Stack(app, 'Test'); const key = new Key(stack, 'Key'); - new Alias(stack, 'Alias', { targetKey: key, name: 'alias/foo' }); + new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' }); expect(stack).to(haveResource('AWS::KMS::Alias', { AliasName: 'alias/foo', @@ -30,7 +30,7 @@ export = { }); test.throws(() => new Alias(stack, 'Alias', { - name: 'foo', + aliasName: 'foo', targetKey: key })); @@ -47,7 +47,7 @@ export = { }); test.throws(() => new Alias(stack, 'Alias', { - name: 'alias/', + aliasName: 'alias/', targetKey: key })); @@ -64,7 +64,7 @@ export = { }); test.throws(() => new Alias(stack, 'Alias', { - name: 'alias/@Nope', + aliasName: 'alias/@Nope', targetKey: key }), 'a-zA-Z0-9:/_-'); @@ -81,17 +81,17 @@ export = { }); test.throws(() => new Alias(stack, 'Alias', { - name: 'alias/aws/', + aliasName: 'alias/aws/', targetKey: key })); test.throws(() => new Alias(stack, 'Alias', { - name: 'alias/aws/Awesome', + aliasName: 'alias/aws/Awesome', targetKey: key })); test.throws(() => new Alias(stack, 'Alias', { - name: 'alias/AWS/awesome', + aliasName: 'alias/AWS/awesome', targetKey: key })); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts index 2c9379dc60acf..6069440dd7014 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.dynamodb.ts @@ -12,14 +12,14 @@ class DynamoEventSourceTest extends cdk.Stack { const queue = new dynamodb.Table(this, 'T', { partitionKey: { name: 'id', - type: dynamodb.AttributeType.String + type: dynamodb.AttributeType.STRING }, - stream: dynamodb.StreamViewType.NewImage + stream: dynamodb.StreamViewType.NEW_IMAGE }); fn.addEventSource(new DynamoEventSource(queue, { batchSize: 5, - startingPosition: lambda.StartingPosition.TrimHorizon + startingPosition: lambda.StartingPosition.TRIM_HORIZON })); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts index 56ade188b85ce..f4670fe3e341f 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.kinesis.ts @@ -12,7 +12,7 @@ class KinesisEventSourceTest extends cdk.Stack { const stream = new kinesis.Stream(this, 'Q'); fn.addEventSource(new KinesisEventSource(stream, { - startingPosition: lambda.StartingPosition.TrimHorizon + startingPosition: lambda.StartingPosition.TRIM_HORIZON })); } } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.ts index 15cf4291712cd..013d6a959b419 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.ts @@ -13,7 +13,7 @@ class S3EventSourceTest extends cdk.Stack { }); fn.addEventSource(new S3EventSource(bucket, { - events: [ s3.EventType.ObjectCreated ], + events: [ s3.EventType.OBJECT_CREATED ], filters: [ { prefix: 'subdir/' } ] })); } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json index 6a50e98575458..1134471c0d6b7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json @@ -60,7 +60,7 @@ "FServiceRole3AC82EE1" ] }, - "FTSubscription775EAF05": { + "TF2453034D": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts index 90afc443559a5..e51cbf01d32d9 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.dynamo.ts @@ -16,14 +16,14 @@ export = { const table = new dynamodb.Table(stack, 'T', { partitionKey: { name: 'id', - type: dynamodb.AttributeType.String + type: dynamodb.AttributeType.STRING }, - stream: dynamodb.StreamViewType.NewImage + stream: dynamodb.StreamViewType.NEW_IMAGE }); // WHEN fn.addEventSource(new sources.DynamoEventSource(table, { - startingPosition: lambda.StartingPosition.TrimHorizon + startingPosition: lambda.StartingPosition.TRIM_HORIZON })); // THEN @@ -82,15 +82,15 @@ export = { const table = new dynamodb.Table(stack, 'T', { partitionKey: { name: 'id', - type: dynamodb.AttributeType.String + type: dynamodb.AttributeType.STRING }, - stream: dynamodb.StreamViewType.NewImage + stream: dynamodb.StreamViewType.NEW_IMAGE }); // WHEN fn.addEventSource(new sources.DynamoEventSource(table, { batchSize: 50, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })); // THEN @@ -118,15 +118,15 @@ export = { const table = new dynamodb.Table(stack, 'T', { partitionKey: { name: 'id', - type: dynamodb.AttributeType.String + type: dynamodb.AttributeType.STRING }, - stream: dynamodb.StreamViewType.NewImage + stream: dynamodb.StreamViewType.NEW_IMAGE }); // WHEN test.throws(() => fn.addEventSource(new sources.DynamoEventSource(table, { batchSize: 0, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })), /Maximum batch size must be between 1 and 1000 inclusive \(given 0\)/); test.done(); @@ -139,15 +139,15 @@ export = { const table = new dynamodb.Table(stack, 'T', { partitionKey: { name: 'id', - type: dynamodb.AttributeType.String + type: dynamodb.AttributeType.STRING }, - stream: dynamodb.StreamViewType.NewImage + stream: dynamodb.StreamViewType.NEW_IMAGE }); // WHEN test.throws(() => fn.addEventSource(new sources.DynamoEventSource(table, { batchSize: 1001, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })), /Maximum batch size must be between 1 and 1000 inclusive \(given 1001\)/); test.done(); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts index b9f95f6522f4a..d28e90f4daabb 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.kinesis.ts @@ -17,7 +17,7 @@ export = { // WHEN fn.addEventSource(new sources.KinesisEventSource(stream, { - startingPosition: lambda.StartingPosition.TrimHorizon + startingPosition: lambda.StartingPosition.TRIM_HORIZON })); // THEN @@ -73,7 +73,7 @@ export = { // WHEN fn.addEventSource(new sources.KinesisEventSource(stream, { batchSize: 50, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })); // THEN @@ -103,7 +103,7 @@ export = { // WHEN test.throws(() => fn.addEventSource(new sources.KinesisEventSource(stream, { batchSize: 0, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })), /Maximum batch size must be between 1 and 10000 inclusive \(given 0\)/); test.done(); @@ -118,7 +118,7 @@ export = { // WHEN test.throws(() => fn.addEventSource(new sources.KinesisEventSource(stream, { batchSize: 10001, - startingPosition: lambda.StartingPosition.Latest + startingPosition: lambda.StartingPosition.LATEST })), /Maximum batch size must be between 1 and 10000 inclusive \(given 10001\)/); test.done(); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts index ef0a192ac618b..e8c11a5aa1999 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.s3.ts @@ -16,7 +16,7 @@ export = { // WHEN fn.addEventSource(new sources.S3EventSource(bucket, { - events: [ s3.EventType.ObjectCreated, s3.EventType.ObjectRemoved ], + events: [ s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED ], filters: [ { prefix: 'prefix/' }, { suffix: '.png' } diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 59b02da68b1cb..80da9c9c992a6 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,5 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, PhysicalName, Stack } from '@aws-cdk/cdk'; import { IFunction, QualifiedFunctionBase } from './function-base'; import { IVersion } from './lambda-version'; import { CfnAlias } from './lambda.generated'; @@ -114,25 +114,38 @@ export class Alias extends QualifiedFunctionBase implements IAlias { protected readonly canCreatePermissions: boolean = true; constructor(scope: Construct, id: string, props: AliasProps) { - super(scope, id); + super(scope, id, { + physicalName: PhysicalName.of(props.aliasName), + }); this.lambda = props.version.lambda; - this.aliasName = props.aliasName; + this.aliasName = this.physicalName; this.version = props.version; const alias = new CfnAlias(this, 'Resource', { - name: props.aliasName, + name: this.aliasName, description: props.description, functionName: this.version.lambda.functionName, functionVersion: props.version.version, routingConfig: this.determineRoutingConfig(props) }); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: alias.ref, + name: this.aliasName, + arnComponents: { + service: 'lambda', + resource: 'function', + resourceName: `${this.lambda.functionName}:${this.physicalName}`, + sep: ':', + }, + }); + + this.functionArn = resourceIdentifiers.arn; // ARN parsing splits on `:`, so we can only get the function's name from the ARN as resourceName... // And we're parsing it out (instead of using the underlying function directly) in order to have use of it incur // an implicit dependency on the resource. - this.functionName = `${Stack.of(this).parseArn(alias.refAsString, ":").resourceName!}:${props.aliasName}`; - this.functionArn = alias.refAsString; + this.functionName = `${Stack.of(this).parseArn(this.functionArn, ":").resourceName!}:${this.aliasName}`; } public get grantPrincipal() { diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index cd71bfa79fb99..25d7e2312843b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -82,11 +82,11 @@ export enum StartingPosition { * Start reading at the last untrimmed record in the shard in the system, * which is the oldest data record in the shard. */ - TrimHorizon = 'TRIM_HORIZON', + TRIM_HORIZON = 'TRIM_HORIZON', /** * Start reading just after the most recent record in the shard, so that you * always read the most recent data in the shard */ - Latest = 'LATEST', + LATEST = 'LATEST', } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index f880d2816f16d..a335bd4ef24f0 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -3,7 +3,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import logs = require('@aws-cdk/aws-logs'); import sqs = require('@aws-cdk/aws-sqs'); -import { Construct, Fn, Lazy, Stack, Token } from '@aws-cdk/cdk'; +import { Construct, Duration, Fn, Lazy, PhysicalName, Stack, Token } from '@aws-cdk/cdk'; import { Code } from './code'; import { IEventSource } from './event-source'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; @@ -21,16 +21,16 @@ export enum Tracing { * Lambda will respect any tracing header it receives from an upstream service. * If no tracing header is received, Lambda will call X-Ray for a tracing decision. */ - Active, + ACTIVE = "Active", /** * Lambda will only trace the request from an upstream service * if it contains a tracing header with "sampled=1" */ - PassThrough, + PASS_THROUGH = "PassThrough", /** * Lambda will not trace any request. */ - Disabled + DISABLED = "Disabled" } export interface FunctionProps { @@ -64,9 +64,9 @@ export interface FunctionProps { * the function. Because the execution time affects cost, set this value * based on the function's expected execution time. * - * @default 3 + * @default Duration.seconds(3) */ - readonly timeout?: number; + readonly timeout?: Duration; /** * Key-value pairs that Lambda caches and makes available for your Lambda @@ -91,7 +91,7 @@ export interface FunctionProps { * @default - AWS CloudFormation generates a unique physical ID and uses that * ID for the function's name. For more information, see Name Type. */ - readonly functionName?: string; + readonly functionName?: PhysicalName; /** * The amount of memory, in MB, that is allocated to your Lambda function. @@ -219,7 +219,7 @@ export interface FunctionProps { * * @default - Logs never expire. */ - readonly logRetentionDays?: logs.RetentionDays; + readonly logRetention?: logs.RetentionDays; } /** @@ -385,7 +385,9 @@ export class Function extends FunctionBase { private readonly environment?: { [key: string]: any }; constructor(scope: Construct, id: string, props: FunctionProps) { - super(scope, id); + super(scope, id, { + physicalName: props.functionName, + }); this.environment = props.environment || { }; @@ -417,12 +419,12 @@ export class Function extends FunctionBase { } const resource: CfnFunction = new CfnFunction(this, 'Resource', { - functionName: props.functionName, + functionName: this.physicalName, description: props.description, code: Lazy.anyValue({ produce: () => props.code._toJSON(resource) }), layers: Lazy.listValue({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), handler: props.handler, - timeout: props.timeout, + timeout: props.timeout && props.timeout.toSeconds(), runtime: props.runtime.name, role: this.role.roleArn, environment: Lazy.anyValue({ produce: () => this.renderEnvironment() }), @@ -435,8 +437,18 @@ export class Function extends FunctionBase { resource.node.addDependency(this.role); - this.functionName = resource.refAsString; - this.functionArn = resource.attrArn; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.attrArn, + name: resource.ref, + arnComponents: { + service: 'lambda', + resource: 'function', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.functionName = resourceIdentifiers.name; + this.functionArn = resourceIdentifiers.arn; this.runtime = props.runtime; // allow code to bind to stack. @@ -451,10 +463,10 @@ export class Function extends FunctionBase { } // Log retention - if (props.logRetentionDays) { + if (props.logRetention) { new LogRetention(this, 'LogRetention', { logGroupName: `/aws/lambda/${this.functionName}`, - retentionDays: props.logRetentionDays + retention: props.logRetention }); } } @@ -585,7 +597,7 @@ export class Function extends FunctionBase { } const deadLetterQueue = props.deadLetterQueue || new sqs.Queue(this, 'DeadLetterQueue', { - retentionPeriodSec: 1209600 + retentionPeriod: Duration.days(14) }); this.addToRolePolicy(new iam.PolicyStatement({ @@ -599,7 +611,7 @@ export class Function extends FunctionBase { } private buildTracingConfig(props: FunctionProps) { - if (props.tracing === undefined || props.tracing === Tracing.Disabled) { + if (props.tracing === undefined || props.tracing === Tracing.DISABLED) { return undefined; } @@ -609,7 +621,7 @@ export class Function extends FunctionBase { })); return { - mode: Tracing[props.tracing] + mode: props.tracing }; } } diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index fe354288e383e..c8b0daa36425b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -105,7 +105,7 @@ export class Version extends QualifiedFunctionBase implements IVersion { }); this.version = version.attrVersion; - this.functionArn = version.refAsString; + this.functionArn = version.ref; this.functionName = `${this.lambda.functionName}:${this.version}`; } diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index d83a6eb97bcf9..ba8f4d20bc90f 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { Code } from './code'; import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; import { Runtime } from './runtime'; @@ -35,7 +35,7 @@ export interface LayerVersionProps { * * @default - A name will be generated. */ - readonly name?: string; + readonly layerVersionName?: PhysicalName; } export interface ILayerVersion extends IResource { @@ -159,7 +159,10 @@ export class LayerVersion extends LayerVersionBase { public readonly compatibleRuntimes?: Runtime[]; constructor(scope: Construct, id: string, props: LayerVersionProps) { - super(scope, id); + super(scope, id, { + physicalName: props.layerVersionName, + }); + if (props.compatibleRuntimes && props.compatibleRuntimes.length === 0) { throw new Error('Attempted to define a Lambda layer that supports no runtime!'); } @@ -173,11 +176,11 @@ export class LayerVersion extends LayerVersionBase { compatibleRuntimes: props.compatibleRuntimes && props.compatibleRuntimes.map(r => r.name), content: Lazy.anyValue({ produce: () => props.code._toJSON(resource) }), description: props.description, - layerName: props.name, + layerName: this.physicalName, licenseInfo: props.license, }); - this.layerVersionArn = resource.refAsString; + this.layerVersionArn = resource.ref; this.compatibleRuntimes = props.compatibleRuntimes; } } diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts index bd4d3ab20ebc0..0e49857094f96 100644 --- a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -18,7 +18,7 @@ export interface LogRetentionProps { /** * The number of days log events are kept in CloudWatch Logs. */ - readonly retentionDays: logs.RetentionDays; + readonly retention: logs.RetentionDays; } /** @@ -55,7 +55,7 @@ export class LogRetention extends cdk.Construct { properties: { ServiceToken: provider.functionArn, LogGroupName: props.logGroupName, - RetentionInDays: props.retentionDays === Infinity ? undefined : props.retentionDays + RetentionInDays: props.retention === Infinity ? undefined : props.retention } }); } diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 14c311d32595a..2c613c9f665bc 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -8,12 +8,12 @@ export interface LambdaRuntimeProps { export enum RuntimeFamily { NodeJS, - Java, - Python, - DotNetCore, - Go, - Ruby, - Other + JAVA, + PYTHON, + DOTNET_CORE, + GO, + RUBY, + OTHER } /** @@ -34,17 +34,17 @@ export class Runtime { public static readonly Nodejs610 = new Runtime('nodejs6.10', RuntimeFamily.NodeJS, { supportsInlineCode: true }); public static readonly Nodejs810 = new Runtime('nodejs8.10', RuntimeFamily.NodeJS, { supportsInlineCode: true }); public static readonly Nodejs10x = new Runtime('nodejs10.x', RuntimeFamily.NodeJS, { supportsInlineCode: false }); - public static readonly Python27 = new Runtime('python2.7', RuntimeFamily.Python, { supportsInlineCode: true }); - public static readonly Python36 = new Runtime('python3.6', RuntimeFamily.Python, { supportsInlineCode: true }); - public static readonly Python37 = new Runtime('python3.7', RuntimeFamily.Python, { supportsInlineCode: true }); - public static readonly Java8 = new Runtime('java8', RuntimeFamily.Java); - public static readonly DotNetCore1 = new Runtime('dotnetcore1.0', RuntimeFamily.DotNetCore); + public static readonly Python27 = new Runtime('python2.7', RuntimeFamily.PYTHON, { supportsInlineCode: true }); + public static readonly Python36 = new Runtime('python3.6', RuntimeFamily.PYTHON, { supportsInlineCode: true }); + public static readonly Python37 = new Runtime('python3.7', RuntimeFamily.PYTHON, { supportsInlineCode: true }); + public static readonly Java8 = new Runtime('java8', RuntimeFamily.JAVA); + public static readonly DotNetCore1 = new Runtime('dotnetcore1.0', RuntimeFamily.DOTNET_CORE); /** @deprecated Use `DotNetCore21` */ - public static readonly DotNetCore2 = new Runtime('dotnetcore2.0', RuntimeFamily.DotNetCore); - public static readonly DotNetCore21 = new Runtime('dotnetcore2.1', RuntimeFamily.DotNetCore); - public static readonly Go1x = new Runtime('go1.x', RuntimeFamily.Go); - public static readonly Ruby25 = new Runtime('ruby2.5', RuntimeFamily.Ruby, { supportsInlineCode: true }); - public static readonly Provided = new Runtime('provided', RuntimeFamily.Other); + public static readonly DotNetCore2 = new Runtime('dotnetcore2.0', RuntimeFamily.DOTNET_CORE); + public static readonly DotNetCore21 = new Runtime('dotnetcore2.1', RuntimeFamily.DOTNET_CORE); + public static readonly Go1x = new Runtime('go1.x', RuntimeFamily.GO); + public static readonly Ruby25 = new Runtime('ruby2.5', RuntimeFamily.RUBY, { supportsInlineCode: true }); + public static readonly Provided = new Runtime('provided', RuntimeFamily.OTHER); /** * The name of this runtime, as expected by the Lambda resource. diff --git a/packages/@aws-cdk/aws-lambda/package-lock.json b/packages/@aws-cdk/aws-lambda/package-lock.json index 974dbb034a5d8..d3a6068708959 100644 --- a/packages/@aws-cdk/aws-lambda/package-lock.json +++ b/packages/@aws-cdk/aws-lambda/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index b87cdbd4765d5..af41c768f6f9c 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -112,7 +112,9 @@ }, "awslint": { "exclude": [ - "integ-return-type:@aws-cdk/aws-lambda.IEventSource.bind" + "integ-return-type:@aws-cdk/aws-lambda.IEventSource.bind", + "props-physical-name:@aws-cdk/aws-lambda.VersionProps", + "props-physical-name:@aws-cdk/aws-lambda.EventSourceMappingProps" ] }, "stability": "experimental" diff --git a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts index 9f2e60e68e79f..c9ada091a6b14 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts @@ -10,21 +10,21 @@ new lambda.Function(stack, 'OneWeek', { code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - logRetentionDays: logs.RetentionDays.OneWeek + logRetention: logs.RetentionDays.ONE_WEEK }); new lambda.Function(stack, 'OneMonth', { code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - logRetentionDays: logs.RetentionDays.OneMonth + logRetention: logs.RetentionDays.ONE_MONTH }); new lambda.Function(stack, 'OneYear', { code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - logRetentionDays: logs.RetentionDays.OneYear + logRetention: logs.RetentionDays.ONE_YEAR }); app.synth(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.alias.ts b/packages/@aws-cdk/aws-lambda/test/test.alias.ts index 14d53123702fb..5eb7398ee2865 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.alias.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.alias.ts @@ -1,6 +1,6 @@ import { beASupersetOfTemplate, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import lambda = require('../lib'); @@ -169,7 +169,7 @@ export = { // WHEN new cloudwatch.Alarm(stack, 'Alarm', { metric: alias.metric('Test'), - alarmName: 'Test', + alarmName: PhysicalName.of('Test'), threshold: 1, evaluationPeriods: 1 }); diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index bc29497c4f59c..9a81fd8a69c34 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -294,7 +294,7 @@ export = { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - functionName: 'OneFunctionToRuleThemAll', + functionName: cdk.PhysicalName.of('OneFunctionToRuleThemAll'), deadLetterQueueEnabled: true }); @@ -582,8 +582,8 @@ export = { const stack = new cdk.Stack(); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriodSec: 1209600 + queueName: cdk.PhysicalName.of('MyLambda_DLQ'), + retentionPeriod: cdk.Duration.days(14) }); new lambda.Function(stack, 'MyLambda', { @@ -691,8 +691,8 @@ export = { const stack = new cdk.Stack(); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriodSec: 1209600 + queueName: cdk.PhysicalName.of('MyLambda_DLQ'), + retentionPeriod: cdk.Duration.days(14) }); new lambda.Function(stack, 'MyLambda', { @@ -801,8 +801,8 @@ export = { const stack = new cdk.Stack(); const dlQueue = new sqs.Queue(stack, 'DeadLetterQueue', { - queueName: 'MyLambda_DLQ', - retentionPeriodSec: 1209600 + queueName: cdk.PhysicalName.of('MyLambda_DLQ'), + retentionPeriod: cdk.Duration.days(14), }); test.throws(() => new lambda.Function(stack, 'MyLambda', { @@ -823,7 +823,7 @@ export = { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - tracing: lambda.Tracing.Active + tracing: lambda.Tracing.ACTIVE }); expect(stack).to(haveResource('AWS::IAM::Policy', { @@ -881,7 +881,7 @@ export = { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - tracing: lambda.Tracing.PassThrough + tracing: lambda.Tracing.PASS_THROUGH }); expect(stack).to(haveResource('AWS::IAM::Policy', { @@ -939,7 +939,7 @@ export = { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, - tracing: lambda.Tracing.Disabled + tracing: lambda.Tracing.DISABLED }); expect(stack).notTo(haveResource('AWS::IAM::Policy', { @@ -1088,7 +1088,7 @@ export = { dimensions: { FunctionName: { Ref: 'Function76856677' }}, namespace: 'AWS/Lambda', metricName: 'Errors', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Sum', }); @@ -1323,7 +1323,7 @@ export = { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.Nodejs, - logRetentionDays: logs.RetentionDays.OneMonth + logRetention: logs.RetentionDays.ONE_MONTH }); // THEN diff --git a/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts index 7cfacdbd3a638..94fe1791b9303 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts @@ -14,7 +14,7 @@ export = { // WHEN new LogRetention(stack, 'MyLambda', { logGroupName: 'group', - retentionDays: logs.RetentionDays.OneMonth + retention: logs.RetentionDays.ONE_MONTH }); // THEN diff --git a/packages/@aws-cdk/aws-lambda/test/test.runtime.ts b/packages/@aws-cdk/aws-lambda/test/test.runtime.ts index a943810c1e88d..c24c06f261fd9 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.runtime.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.runtime.ts @@ -5,8 +5,8 @@ import lambda = require('../lib'); export = testCase({ 'runtimes are equal for different instances'(test: Test) { // GIVEN - const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); - const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); + const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); + const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); // WHEN const result = runtime1.runtimeEquals(runtime2); @@ -18,7 +18,7 @@ export = testCase({ }, 'runtimes are equal for same instance'(test: Test) { // GIVEN - const runtime = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); + const runtime = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); // WHEN const result = runtime.runtimeEquals(runtime); @@ -30,8 +30,8 @@ export = testCase({ }, 'unequal when name changes'(test: Test) { // GIVEN - const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); - const runtime2 = new lambda.Runtime('python3.6', RuntimeFamily.Python, {supportsInlineCode: true}); + const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); + const runtime2 = new lambda.Runtime('python3.6', RuntimeFamily.PYTHON, {supportsInlineCode: true}); // WHEN const result = runtime1.runtimeEquals(runtime2); @@ -43,8 +43,8 @@ export = testCase({ }, 'unequal when family changes'(test: Test) { // GIVEN - const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); - const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.Java, {supportsInlineCode: true}); + const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); + const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.JAVA, {supportsInlineCode: true}); // WHEN const result = runtime1.runtimeEquals(runtime2); @@ -56,8 +56,8 @@ export = testCase({ }, 'unequal when supportsInlineCode changes'(test: Test) { // GIVEN - const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: true}); - const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.Python, {supportsInlineCode: false}); + const runtime1 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: true}); + const runtime2 = new lambda.Runtime('python3.7', RuntimeFamily.PYTHON, {supportsInlineCode: false}); // WHEN const result = runtime1.runtimeEquals(runtime2); diff --git a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts index 5a49886d7658b..74b02f61e6890 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.singleton-lambda.ts @@ -15,7 +15,7 @@ export = { code: new lambda.InlineCode('def hello(): pass'), runtime: lambda.Runtime.Python27, handler: 'index.hello', - timeout: 300, + timeout: cdk.Duration.minutes(5), }); } diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index b96ef156bf1c6..ffebc2289da5a 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -50,7 +50,7 @@ export = { const somethingConnectable = new SomethingConnectable(new ec2.Connections({ securityGroups: [securityGroup] })); // WHEN - this.lambda.connections.allowTo(somethingConnectable, new ec2.TcpAllPorts(), 'Lambda can call connectable'); + this.lambda.connections.allowTo(somethingConnectable, ec2.Port.allTcp(), 'Lambda can call connectable'); // THEN: Lambda can connect to SomeSecurityGroup expect(this.stack).to(haveResource("AWS::EC2::SecurityGroupEgress", { @@ -82,7 +82,7 @@ export = { const somethingConnectable = new SomethingConnectable(new ec2.Connections({ securityGroups: [securityGroup] })); // WHEN - somethingConnectable.connections.allowFrom(this.lambda.connections, new ec2.TcpAllPorts(), 'Lambda can call connectable'); + somethingConnectable.connections.allowFrom(this.lambda.connections, ec2.Port.allTcp(), 'Lambda can call connectable'); // THEN: SomeSecurityGroup accepts connections from Lambda expect(stack2).to(haveResource("AWS::EC2::SecurityGroupEgress", { @@ -133,7 +133,7 @@ export = { // WHEN test.throws(() => { - lambdaFn.connections.allowToAnyIPv4(new ec2.TcpAllPorts(), 'Reach for the world Lambda!'); + lambdaFn.connections.allowToAnyIPv4(ec2.Port.allTcp(), 'Reach for the world Lambda!'); }); test.done(); @@ -151,7 +151,7 @@ export = { handler: 'index.handler', runtime: lambda.Runtime.Nodejs810, vpc, - vpcSubnets: { subnetType: ec2.SubnetType.Public } + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC } }); }); diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index 865ad2ecfe9c1..67fd7fc262668 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { Construct, Lazy, Stack } from '@aws-cdk/cdk'; +import { Construct, Lazy, PhysicalName, Stack } from '@aws-cdk/cdk'; import { ILogGroup } from './log-group'; import { CfnDestination } from './logs.generated'; import { ILogSubscriptionDestination, LogSubscriptionDestinationConfig } from './subscription-filter'; @@ -14,7 +14,7 @@ export interface CrossAccountDestinationProps { * * @default Automatically generated */ - readonly destinationName?: string; + readonly destinationName?: PhysicalName; /** * The role to assume that grants permissions to write to 'target'. @@ -66,21 +66,32 @@ export class CrossAccountDestination extends cdk.Resource implements ILogSubscri private readonly resource: CfnDestination; constructor(scope: cdk.Construct, id: string, props: CrossAccountDestinationProps) { - super(scope, id); - - // In the underlying model, the name is not optional, but we make it so anyway. - const destinationName = props.destinationName || Lazy.stringValue({ produce: () => this.generateUniqueName() }); + super(scope, id, { + physicalName: props.destinationName || + // In the underlying model, the name is not optional, but we make it so anyway. + PhysicalName.of(Lazy.stringValue({ produce: () => this.generateUniqueName() })), + }); this.resource = new CfnDestination(this, 'Resource', { - destinationName, + destinationName: this.physicalName!, // Must be stringified policy destinationPolicy: this.lazyStringifiedPolicyDocument(), roleArn: props.role.roleArn, targetArn: props.targetArn }); - this.destinationArn = this.resource.attrArn; - this.destinationName = this.resource.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: this.resource.attrArn, + name: this.resource.ref, + arnComponents: { + service: 'logs', + resource: 'destination', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.destinationArn = resourceIdentifiers.arn; + this.destinationName = resourceIdentifiers.name; } public addToPolicy(statement: iam.PolicyStatement) { diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index 025f9d50c0a35..aab3530da10b1 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -1,6 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); -import { Construct, IResource, RemovalPolicy, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, RemovalPolicy, Resource, Stack } from '@aws-cdk/cdk'; import { LogStream } from './log-stream'; import { CfnLogGroup } from './logs.generated'; import { MetricFilter } from './metric-filter'; @@ -179,87 +179,87 @@ export enum RetentionDays { /** * 1 day */ - OneDay = 1, + ONE_DAY = 1, /** * 3 days */ - ThreeDays = 3, + THREE_DAYS = 3, /** * 5 days */ - FiveDays = 5, + FIVE_DAYS = 5, /** * 1 week */ - OneWeek = 7, + ONE_WEEK = 7, /** * 2 weeks */ - TwoWeeks = 14, + TWO_WEEKS = 14, /** * 1 month */ - OneMonth = 30, + ONE_MONTH = 30, /** * 2 months */ - TwoMonths = 60, + TWO_MONTHS = 60, /** * 3 months */ - ThreeMonths = 90, + THREE_MONTHS = 90, /** * 4 months */ - FourMonths = 120, + FOUR_MONTHS = 120, /** * 5 months */ - FiveMonths = 150, + FIVE_MONTHS = 150, /** * 6 months */ - SixMonths = 180, + SIX_MONTHS = 180, /** * 1 year */ - OneYear = 365, + ONE_YEAR = 365, /** * 13 months */ - ThirteenMonths = 400, + THIRTEEN_MONTHS = 400, /** * 18 months */ - EighteenMonths = 545, + EIGHTEEN_MONTHS = 545, /** * 2 years */ - TwoYears = 731, + TWO_YEARS = 731, /** * 5 years */ - FiveYears = 1827, + FIVE_YEARS = 1827, /** * 10 years */ - TenYears = 3653 + TEN_YEARS = 3653 } /** @@ -271,16 +271,16 @@ export interface LogGroupProps { * * @default Automatically generated */ - readonly logGroupName?: string; + readonly logGroupName?: PhysicalName; /** * How long, in days, the log contents will be retained. * * To retain all logs, set this value to Infinity. * - * @default 731 days (2 years) + * @default RetentionDays.TwoYears */ - readonly retentionDays?: RetentionDays; + readonly retention?: RetentionDays; /** * Determine the removal policy of this log group. @@ -322,10 +322,12 @@ export class LogGroup extends LogGroupBase { public readonly logGroupName: string; constructor(scope: Construct, id: string, props: LogGroupProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.logGroupName, + }); - let retentionInDays = props.retentionDays; - if (retentionInDays === undefined) { retentionInDays = RetentionDays.TwoYears; } + let retentionInDays = props.retention; + if (retentionInDays === undefined) { retentionInDays = RetentionDays.TWO_YEARS; } if (retentionInDays === Infinity) { retentionInDays = undefined; } if (retentionInDays !== undefined && retentionInDays <= 0) { @@ -333,14 +335,24 @@ export class LogGroup extends LogGroupBase { } const resource = new CfnLogGroup(this, 'Resource', { - logGroupName: props.logGroupName, + logGroupName: this.physicalName, retentionInDays, }); resource.applyRemovalPolicy(props.removalPolicy); - this.logGroupArn = resource.attrArn; - this.logGroupName = resource.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.attrArn, + name: resource.ref, + arnComponents: { + service: 'logs', + resource: 'log-group', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.logGroupArn = resourceIdentifiers.arn; + this.logGroupName = resourceIdentifiers.name; } } @@ -355,7 +367,7 @@ export interface StreamOptions { * * @default Automatically generated */ - readonly logStreamName?: string; + readonly logStreamName?: PhysicalName; } /** diff --git a/packages/@aws-cdk/aws-logs/lib/log-stream.ts b/packages/@aws-cdk/aws-logs/lib/log-stream.ts index 4c864f0dc699e..a2efd00008f19 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-stream.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-stream.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, RemovalPolicy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, RemovalPolicy, Resource } from '@aws-cdk/cdk'; import { ILogGroup } from './log-group'; import { CfnLogStream } from './logs.generated'; @@ -26,7 +26,7 @@ export interface LogStreamProps { * * @default Automatically generated */ - readonly logStreamName?: string; + readonly logStreamName?: PhysicalName; /** * Determine what happens when the log stream resource is removed from the @@ -64,15 +64,27 @@ export class LogStream extends Resource implements ILogStream { public readonly logStreamName: string; constructor(scope: Construct, id: string, props: LogStreamProps) { - super(scope, id); + super(scope, id, { + physicalName: props.logStreamName, + }); const resource = new CfnLogStream(this, 'Resource', { logGroupName: props.logGroup.logGroupName, - logStreamName: props.logStreamName + logStreamName: this.physicalName, }); resource.applyRemovalPolicy(props.removalPolicy); - this.logStreamName = resource.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: '', + name: resource.ref, + arnComponents: { + service: 'logs', + resource: 'log-group', + resourceName: `log-stream:${this.physicalName}`, + sep: ':', + }, + }); + this.logStreamName = resourceIdentifiers.name; } } diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index 74bff11e1375a..8187007490984 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -85,8 +85,10 @@ }, "awslint": { "exclude": [ - "props-no-arn-refs:@aws-cdk/aws-logs.CrossAccountDestinationProps.targetArn" + "props-no-arn-refs:@aws-cdk/aws-logs.CrossAccountDestinationProps.targetArn", + "props-physical-name:@aws-cdk/aws-logs.MetricFilterProps", + "props-physical-name:@aws-cdk/aws-logs.SubscriptionFilterProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts b/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts index ed0fd084e18d4..230eec2e165a5 100644 --- a/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts +++ b/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts @@ -7,7 +7,7 @@ function shortLogGroup() { /// !show // Configure log group for short retention const logGroup = new LogGroup(stack, 'LogGroup', { - retentionDays: RetentionDays.OneWeek + retention: RetentionDays.ONE_WEEK }); /// !hide return logGroup; @@ -17,7 +17,7 @@ function infiniteLogGroup() { /// !show // Configure log group for infinite retention const logGroup = new LogGroup(stack, 'LogGroup', { - retentionDays: Infinity + retention: Infinity }); /// !hide return logGroup; diff --git a/packages/@aws-cdk/aws-logs/test/test.destination.ts b/packages/@aws-cdk/aws-logs/test/test.destination.ts index 9d42a6f3852c4..2e141217ceb71 100644 --- a/packages/@aws-cdk/aws-logs/test/test.destination.ts +++ b/packages/@aws-cdk/aws-logs/test/test.destination.ts @@ -14,7 +14,7 @@ export = { // WHEN new CrossAccountDestination(stack, 'Dest', { - destinationName: 'MyDestination', + destinationName: cdk.PhysicalName.of('MyDestination'), role, targetArn: 'arn:bogus' }); @@ -37,7 +37,7 @@ export = { }); const dest = new CrossAccountDestination(stack, 'Dest', { - destinationName: 'MyDestination', + destinationName: cdk.PhysicalName.of('MyDestination'), role, targetArn: 'arn:bogus' }); diff --git a/packages/@aws-cdk/aws-logs/test/test.loggroup.ts b/packages/@aws-cdk/aws-logs/test/test.loggroup.ts index f1be0c5a64ecd..8fa9c604c12f1 100644 --- a/packages/@aws-cdk/aws-logs/test/test.loggroup.ts +++ b/packages/@aws-cdk/aws-logs/test/test.loggroup.ts @@ -11,7 +11,7 @@ export = { // WHEN new LogGroup(stack, 'LogGroup', { - retentionDays: RetentionDays.OneWeek + retention: RetentionDays.ONE_WEEK }); // THEN @@ -43,7 +43,7 @@ export = { // WHEN new LogGroup(stack, 'LogGroup', { - retentionDays: Infinity + retention: Infinity }); // THEN @@ -65,7 +65,7 @@ export = { // WHEN new LogGroup(stack, 'LogGroup', { - retentionDays: Infinity, + retention: Infinity, removalPolicy: RemovalPolicy.Destroy }); diff --git a/packages/@aws-cdk/aws-quickstarts/.gitignore b/packages/@aws-cdk/aws-quickstarts/.gitignore deleted file mode 100644 index ca94dd35b5aaf..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -*.js -tsconfig.json -tslint.json -*.js.map -*.d.ts -dist -.jsii - -.LAST_BUILD -.LAST_PACKAGE -*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-quickstarts/.jsii b/packages/@aws-cdk/aws-quickstarts/.jsii new file mode 100644 index 0000000000000..09e1b1d3040ba --- /dev/null +++ b/packages/@aws-cdk/aws-quickstarts/.jsii @@ -0,0 +1,53 @@ +{ + "author": { + "name": "Amazon Web Services", + "organization": true, + "roles": [ + "author" + ], + "url": "https://aws.amazon.com" + }, + "description": "AWS Quickstarts for the CDK", + "docs": { + "deprecated": "This module has been deprecated. Use `@aws-cdk/aws-cloudformation.CfnStack` instead", + "stability": "deprecated" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "jsiiVersion": "0.11.2 (build 27d16c2)", + "license": "Apache-2.0", + "name": "@aws-cdk/aws-quickstarts", + "readme": { + "markdown": "## AWS Quickstarts for the CDK\n\n\n---\n\n![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge)\n\n> This API may emit warnings. Backward compatibility is not guaranteed.\n\n---\n\n\nThis module has been deprecated. Use `@aws-cdk/aws-cloudformation.CfnStack` instead.\n\nThis module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.\n" + }, + "repository": { + "directory": "packages/@aws-cdk/aws-quickstarts", + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "schema": "jsii/0.10.0", + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.AWS.QuickStarts", + "packageId": "Amazon.CDK.AWS.QuickStarts", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "quickstarts", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.services.quickstarts" + }, + "js": { + "npm": "@aws-cdk/aws-quickstarts" + }, + "python": { + "distName": "aws-cdk.aws-quickstarts", + "module": "aws_cdk.aws_quickstarts" + } + }, + "types": {}, + "version": "0.34.0", + "fingerprint": "CDRii8gRfwjoAY+MRoVtkfuuNeyfYCYTB0Y7EyqlFUQ=" +} diff --git a/packages/@aws-cdk/aws-quickstarts/.npmignore b/packages/@aws-cdk/aws-quickstarts/.npmignore deleted file mode 100644 index f5a63a96df103..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/.npmignore +++ /dev/null @@ -1,18 +0,0 @@ -# Don't include original .ts files when doing `npm pack` -*.ts -!*.d.ts -coverage -.nyc_output -*.tgz - -dist -.LAST_PACKAGE -.LAST_BUILD -!*.js - -# Include .jsii -!.jsii - -*.snk - -*.tsbuildinfo diff --git a/packages/@aws-cdk/aws-quickstarts/LICENSE b/packages/@aws-cdk/aws-quickstarts/LICENSE deleted file mode 100644 index 46c185646b439..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2018-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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/@aws-cdk/aws-quickstarts/NOTICE b/packages/@aws-cdk/aws-quickstarts/NOTICE deleted file mode 100644 index 8585168af8b7d..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ -AWS Cloud Development Kit (AWS CDK) -Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-quickstarts/README.md b/packages/@aws-cdk/aws-quickstarts/README.md deleted file mode 100644 index e187015a4fd51..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## AWS Quickstarts for the CDK - - ---- - -![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge) - -> This API may emit warnings. Backward compatibility is not guaranteed. - ---- - - -This module has been deprecated. Use `@aws-cdk/aws-cloudformation.CfnStack` instead. - -This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. diff --git a/packages/@aws-cdk/aws-quickstarts/lib/index.d.ts b/packages/@aws-cdk/aws-quickstarts/lib/index.d.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-quickstarts/lib/index.js b/packages/@aws-cdk/aws-quickstarts/lib/index.js new file mode 100644 index 0000000000000..a7b1947e58505 --- /dev/null +++ b/packages/@aws-cdk/aws-quickstarts/lib/index.js @@ -0,0 +1,4 @@ +"use strict"; +// tslint:disable-next-line:no-console +console.error(`ERROR: The @aws-cdk/aws-quickstarts module has been deprecated`); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsc0NBQXNDO0FBQ3RDLE9BQU8sQ0FBQyxLQUFLLENBQUMsZ0VBQWdFLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIHRzbGludDpkaXNhYmxlLW5leHQtbGluZTpuby1jb25zb2xlXG5jb25zb2xlLmVycm9yKGBFUlJPUjogVGhlIEBhd3MtY2RrL2F3cy1xdWlja3N0YXJ0cyBtb2R1bGUgaGFzIGJlZW4gZGVwcmVjYXRlZGApO1xuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-quickstarts/lib/index.ts b/packages/@aws-cdk/aws-quickstarts/lib/index.ts deleted file mode 100644 index bc1a13d260a6d..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/lib/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// tslint:disable-next-line:no-console -console.error(`ERROR: The @aws-cdk/aws-quickstarts module has been deprecated`); diff --git a/packages/@aws-cdk/aws-quickstarts/package-lock.json b/packages/@aws-cdk/aws-quickstarts/package-lock.json deleted file mode 100644 index 995856da05bfd..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@aws-cdk/aws-quickstarts", - "version": "0.33.0", - "lockfileVersion": 1 -} diff --git a/packages/@aws-cdk/aws-quickstarts/package.json b/packages/@aws-cdk/aws-quickstarts/package.json deleted file mode 100644 index 89eeb9f120c2a..0000000000000 --- a/packages/@aws-cdk/aws-quickstarts/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "@aws-cdk/aws-quickstarts", - "version": "0.35.0", - "description": "AWS Quickstarts for the CDK", - "deprecated": "This module has been deprecated. Use `@aws-cdk/aws-cloudformation.CfnStack` instead", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.awscdk.services.quickstarts", - "maven": { - "groupId": "software.amazon.awscdk", - "artifactId": "quickstarts" - } - }, - "dotnet": { - "namespace": "Amazon.CDK.AWS.QuickStarts", - "packageId": "Amazon.CDK.AWS.QuickStarts", - "signAssembly": true, - "assemblyOriginatorKeyFile": "../../key.snk" - }, - "python": { - "distName": "aws-cdk.aws-quickstarts", - "module": "aws_cdk.aws_quickstarts" - } - } - }, - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git", - "directory": "packages/@aws-cdk/aws-quickstarts" - }, - "scripts": { - "build": "cdk-build", - "watch": "cdk-watch", - "lint": "cdk-lint", - "pkglint": "pkglint -f", - "test": "cdk-test", - "package": "cdk-package", - "awslint": "cdk-awslint", - "build+test+package": "npm run build+test && npm run package", - "build+test": "npm run build && npm test" - }, - "keywords": [ - "aws", - "cdk", - "constructs", - "quickstarts" - ], - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "devDependencies": { - "cdk-build-tools": "^0.35.0", - "pkglint": "^0.35.0" - }, - "homepage": "https://github.com/awslabs/aws-cdk", - "engines": { - "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "props-default-doc" - ] - }, - "stability": "deprecated" -} diff --git a/packages/@aws-cdk/aws-quickstarts/tsconfig.json b/packages/@aws-cdk/aws-quickstarts/tsconfig.json new file mode 100644 index 0000000000000..be137c10a984b --- /dev/null +++ b/packages/@aws-cdk/aws-quickstarts/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2016", + "es2017.object", + "es2017.string" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "stripInternal": true, + "target": "ES2018", + "composite": true + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "../../../tools/cdk-build-tools" + }, + { + "path": "../../../tools/pkglint" + } + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index e8ea4034692b1..2ee89e606cfb7 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -174,7 +174,7 @@ abstract class DatabaseClusterBase extends Resource implements IDatabaseCluster public asSecretAttachmentTarget(): secretsmanager.SecretAttachmentTargetProps { return { targetId: this.clusterIdentifier, - targetType: secretsmanager.AttachmentTargetType.Cluster + targetType: secretsmanager.AttachmentTargetType.CLUSTER }; } } @@ -190,10 +190,10 @@ export class DatabaseCluster extends DatabaseClusterBase { */ public static fromDatabaseClusterAttributes(scope: Construct, id: string, attrs: DatabaseClusterAttributes): IDatabaseCluster { class Import extends DatabaseClusterBase implements IDatabaseCluster { - public readonly defaultPortRange = new ec2.TcpPort(attrs.port); + public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly connections = new ec2.Connections({ securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroup', attrs.securityGroupId)], - defaultPortRange: this.defaultPortRange + defaultPort: this.defaultPort }); public readonly clusterIdentifier = attrs.clusterIdentifier; public readonly instanceIdentifiers: string[] = []; @@ -301,7 +301,7 @@ export class DatabaseCluster extends DatabaseClusterBase { engine: props.engine.name, engineVersion: props.engineVersion, dbClusterIdentifier: props.clusterIdentifier, - dbSubnetGroupName: subnetGroup.refAsString, + dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: [this.securityGroupId], port: props.port, dbClusterParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, @@ -312,7 +312,7 @@ export class DatabaseCluster extends DatabaseClusterBase { : (props.masterUser.password ? props.masterUser.password.toString() : undefined), - backupRetentionPeriod: props.backup && props.backup.retentionDays, + backupRetentionPeriod: props.backup && props.backup.retention && props.backup.retention.toDays(), preferredBackupWindow: props.backup && props.backup.preferredWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, databaseName: props.defaultDatabaseName, @@ -325,12 +325,12 @@ export class DatabaseCluster extends DatabaseClusterBase { applyToUpdateReplacePolicy: true }); - this.clusterIdentifier = cluster.refAsString; + this.clusterIdentifier = cluster.ref; // create a number token that represents the port of the cluster const portAttribute = Token.asNumber(cluster.attrEndpointPort); this.clusterEndpoint = new Endpoint(cluster.attrEndpointAddress, portAttribute); - this.clusterReadEndpoint = new Endpoint(cluster.attrEndpointAddress, portAttribute); + this.clusterReadEndpoint = new Endpoint(cluster.attrReadEndpointAddress, portAttribute); if (secret) { this.secret = secret.addTargetAttachment('AttachedSecret', { @@ -344,7 +344,7 @@ export class DatabaseCluster extends DatabaseClusterBase { } // Get the actual subnet objects so we can depend on internet connectivity. - const internetConnected = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).internetConnectedDependency; + const internetConnected = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).internetConnectivityEstablished; for (let i = 0; i < instanceCount; i++) { const instanceIndex = i + 1; @@ -352,19 +352,19 @@ export class DatabaseCluster extends DatabaseClusterBase { props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` : undefined; - const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.Public; + const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.PUBLIC; const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster engine: props.engine.name, engineVersion: props.engineVersion, - dbClusterIdentifier: cluster.refAsString, + dbClusterIdentifier: cluster.ref, dbInstanceIdentifier: instanceIdentifier, // Instance properties dbInstanceClass: databaseInstanceType(props.instanceProps.instanceType), publiclyAccessible, // This is already set on the Cluster. Unclear to me whether it should be repeated or not. Better yes. - dbSubnetGroupName: subnetGroup.refAsString, + dbSubnetGroupName: subnetGroup.ref, dbParameterGroupName: props.instanceProps.parameterGroup && props.instanceProps.parameterGroup.parameterGroupName, }); @@ -376,12 +376,12 @@ export class DatabaseCluster extends DatabaseClusterBase { // things in the right order. instance.node.addDependency(internetConnected); - this.instanceIdentifiers.push(instance.refAsString); + this.instanceIdentifiers.push(instance.ref); this.instanceEndpoints.push(new Endpoint(instance.attrEndpointAddress, portAttribute)); } - const defaultPortRange = new ec2.TcpPort(this.clusterEndpoint.port); - this.connections = new ec2.Connections({ securityGroups: [securityGroup], defaultPortRange }); + const defaultPort = ec2.Port.tcp(this.clusterEndpoint.port); + this.connections = new ec2.Connections({ securityGroups: [securityGroup], defaultPort }); } /** diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts index fc157f19a4065..c920e61d01d2b 100644 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ b/packages/@aws-cdk/aws-rds/lib/instance.ts @@ -5,10 +5,10 @@ import kms = require('@aws-cdk/aws-kms'); import lambda = require('@aws-cdk/aws-lambda'); import logs = require('@aws-cdk/aws-logs'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import { Construct, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, RemovalPolicy, Resource, SecretValue, Stack, Token } from '@aws-cdk/cdk'; import { DatabaseSecret } from './database-secret'; import { Endpoint } from './endpoint'; -import { IOptionGroup} from './option-group'; +import { IOptionGroup } from './option-group'; import { IParameterGroup } from './parameter-group'; import { DatabaseClusterEngine } from './props'; import { CfnDBInstance, CfnDBInstanceProps, CfnDBSubnetGroup } from './rds.generated'; @@ -90,10 +90,10 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase */ public static fromDatabaseInstanceAttributes(scope: Construct, id: string, attrs: DatabaseInstanceAttributes): IDatabaseInstance { class Import extends DatabaseInstanceBase implements IDatabaseInstance { - public readonly defaultPortRange = new ec2.TcpPort(attrs.port); + public readonly defaultPort = ec2.Port.tcp(attrs.port); public readonly connections = new ec2.Connections({ securityGroups: [ec2.SecurityGroup.fromSecurityGroupId(this, 'SecurityGroup', attrs.securityGroupId)], - defaultPortRange: this.defaultPortRange + defaultPort: this.defaultPort }); public readonly instanceIdentifier = attrs.instanceIdentifier; public readonly dbInstanceEndpointAddress = attrs.instanceEndpointAddress; @@ -144,7 +144,7 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase public asSecretAttachmentTarget(): secretsmanager.SecretAttachmentTargetProps { return { targetId: this.instanceIdentifier, - targetType: secretsmanager.AttachmentTargetType.Instance + targetType: secretsmanager.AttachmentTargetType.INSTANCE }; } } @@ -174,17 +174,17 @@ export enum LicenseModel { /** * License included. */ - LicenseIncluded = 'license-included', + LICENSE_INCLUDED = 'license-included', /** * Bring your own licencse. */ - BringYourOwnLicense = 'bring-your-own-license', + BRING_YOUR_OWN_LICENSE = 'bring-your-own-license', /** * General public license. */ - GeneralPublicLicense = 'general-public-license' + GENERAL_PUBLIC_LICENSE = 'general-public-license' } /** @@ -209,7 +209,7 @@ export enum StorageType { /** * Standard. */ - Standard = 'standard', + STANDARD = 'standard', /** * General purpose (SSD). @@ -225,16 +225,16 @@ export enum StorageType { /** * The retention period for Performance Insight. */ -export enum PerformanceInsightRetentionPeriod { +export enum PerformanceInsightRetention { /** * Default retention period of 7 days. */ - Default = 7, + DEFAULT = 7, /** * Long term retention period of 2 years. */ - LongTerm = 731 + LONG_TERM = 731 } /** @@ -333,7 +333,7 @@ export interface DatabaseInstanceNewProps { * * @default 1 day */ - readonly backupRetentionPeriod?: number; + readonly backupRetention?: Duration; /** * The daily time range during which automated backups are performed. @@ -372,7 +372,7 @@ export interface DatabaseInstanceNewProps { * * @default no enhanced monitoring */ - readonly monitoringInterval?: number; + readonly monitoringInterval?: Duration; /** * Whether to enable Performance Insights for the DB instance. @@ -386,7 +386,7 @@ export interface DatabaseInstanceNewProps { * * @default 7 days */ - readonly performanceInsightRetentionPeriod?: PerformanceInsightRetentionPeriod; + readonly performanceInsightRetention?: PerformanceInsightRetention; /** * The AWS KMS key for encryption of Performance Insights data. @@ -501,18 +501,18 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData this.newCfnProps = { autoMinorVersionUpgrade: props.autoMinorVersionUpgrade, availabilityZone: props.multiAz ? undefined : props.availabilityZone, - backupRetentionPeriod: props.backupRetentionPeriod !== undefined ? props.backupRetentionPeriod : undefined, + backupRetentionPeriod: props.backupRetention ? props.backupRetention.toDays() : undefined, copyTagsToSnapshot: props.copyTagsToSnapshot !== undefined ? props.copyTagsToSnapshot : true, dbInstanceClass: `db.${props.instanceClass}`, dbInstanceIdentifier: props.instanceIdentifier, - dbSubnetGroupName: subnetGroup.refAsString, + dbSubnetGroupName: subnetGroup.ref, deleteAutomatedBackups: props.deleteAutomatedBackups, deletionProtection, enableCloudwatchLogsExports: this.cloudwatchLogsExports, enableIamDatabaseAuthentication: props.iamAuthentication, enablePerformanceInsights: props.enablePerformanceInsights, iops, - monitoringInterval: props.monitoringInterval, + monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(), monitoringRoleArn: monitoringRole && monitoringRole.roleArn, multiAz: props.multiAz, optionGroupName: props.optionGroup && props.optionGroup.optionGroupName, @@ -520,13 +520,13 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData ? props.performanceInsightKmsKey && props.performanceInsightKmsKey.keyArn : undefined, performanceInsightsRetentionPeriod: props.enablePerformanceInsights - ? (props.performanceInsightRetentionPeriod || PerformanceInsightRetentionPeriod.Default) + ? (props.performanceInsightRetention || PerformanceInsightRetention.DEFAULT) : undefined, port: props.port ? props.port.toString() : undefined, preferredBackupWindow: props.preferredBackupWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, processorFeatures: props.processorFeatures && renderProcessorFeatures(props.processorFeatures), - publiclyAccessible: props.vpcPlacement && props.vpcPlacement.subnetType === ec2.SubnetType.Public, + publiclyAccessible: props.vpcPlacement && props.vpcPlacement.subnetType === ec2.SubnetType.PUBLIC, storageType, vpcSecurityGroups: [this.securityGroupId] }; @@ -537,7 +537,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData for (const log of this.cloudwatchLogsExports) { new lambda.LogRetention(this, `LogRetention${log}`, { logGroupName: `/aws/rds/instance/${this.instanceIdentifier}/${log}`, - retentionDays: this.cloudwatchLogsRetention + retention: this.cloudwatchLogsRetention }); } } @@ -733,7 +733,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas storageEncrypted: props.kmsKey ? true : props.storageEncrypted }); - this.instanceIdentifier = instance.refAsString; + this.instanceIdentifier = instance.ref; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; @@ -753,7 +753,7 @@ export class DatabaseInstance extends DatabaseInstanceSource implements IDatabas this.connections = new ec2.Connections({ securityGroups: [this.securityGroup], - defaultPortRange: new ec2.TcpPort(this.instanceEndpoint.port) + defaultPort: ec2.Port.tcp(this.instanceEndpoint.port) }); this.setLogRetention(); @@ -827,7 +827,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme : undefined), }); - this.instanceIdentifier = instance.refAsString; + this.instanceIdentifier = instance.ref; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; @@ -847,7 +847,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme this.connections = new ec2.Connections({ securityGroups: [this.securityGroup], - defaultPortRange: new ec2.TcpPort(this.instanceEndpoint.port) + defaultPort: ec2.Port.tcp(this.instanceEndpoint.port) }); this.setLogRetention(); @@ -904,7 +904,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements storageEncrypted: props.kmsKey ? true : props.storageEncrypted, }); - this.instanceIdentifier = instance.refAsString; + this.instanceIdentifier = instance.ref; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; this.dbInstanceEndpointPort = instance.attrEndpointPort; @@ -918,7 +918,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements this.connections = new ec2.Connections({ securityGroups: [this.securityGroup], - defaultPortRange: new ec2.TcpPort(this.instanceEndpoint.port) + defaultPort: ec2.Port.tcp(this.instanceEndpoint.port) }); this.setLogRetention(); diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index d18318736d4aa..c5aabc7460923 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -122,7 +122,7 @@ export class OptionGroup extends Resource implements IOptionGroup { optionConfigurations: this.renderConfigurations(props.configurations) }); - this.optionGroupName = optionGroup.refAsString; + this.optionGroupName = optionGroup.ref; } /** @@ -149,7 +149,7 @@ export class OptionGroup extends Resource implements IOptionGroup { this.optionConnections[config.name] = new ec2.Connections({ securityGroups: [securityGroup], - defaultPortRange: new ec2.TcpPort(config.port) + defaultPort: ec2.Port.tcp(config.port) }); configuration = { diff --git a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts index 1bc0722195535..a0be5a2661265 100644 --- a/packages/@aws-cdk/aws-rds/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/parameter-group.ts @@ -85,7 +85,7 @@ export class ParameterGroup extends ParameterGroupBase { parameters: props.parameters, }); - this.parameterGroupName = resource.refAsString; + this.parameterGroupName = resource.ref; } } @@ -116,6 +116,6 @@ export class ClusterParameterGroup extends ParameterGroupBase { parameters: props.parameters, }); - this.parameterGroupName = resource.refAsString; + this.parameterGroupName = resource.ref; } } diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index cde179018c92d..ff26825559cb2 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -1,6 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); -import { SecretValue } from '@aws-cdk/cdk'; +import { Duration, SecretValue } from '@aws-cdk/cdk'; import { IParameterGroup } from './parameter-group'; import { SecretRotationApplication } from './secret-rotation'; @@ -80,7 +80,7 @@ export interface BackupProps { /** * How many days to retain the backup */ - readonly retentionDays: number; + readonly retention: Duration; /** * A daily time range in 24-hours UTC format in which backups preferably execute. diff --git a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts index b17ce3903cbb6..05db13f02baf0 100644 --- a/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/lib/secret-rotation.ts @@ -2,7 +2,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import lambda = require('@aws-cdk/aws-lambda'); import serverless = require('@aws-cdk/aws-sam'); import secretsmanager = require('@aws-cdk/aws-secretsmanager'); -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Stack } from '@aws-cdk/cdk'; /** * A secret rotation serverless application. @@ -40,9 +40,9 @@ export interface SecretRotationOptions { * Specifies the number of days after the previous rotation before * Secrets Manager triggers the next automatic rotation. * - * @default 30 days + * @default Duration.days(30) */ - readonly automaticallyAfterDays?: number; + readonly automaticallyAfter?: Duration; } /** @@ -96,7 +96,7 @@ export class SecretRotation extends Construct { constructor(scope: Construct, id: string, props: SecretRotationProps) { super(scope, id); - if (!props.target.connections.defaultPortRange) { + if (!props.target.connections.defaultPort) { throw new Error('The `target` connections must have a default port range.'); } @@ -139,7 +139,7 @@ export class SecretRotation extends Construct { const rotationSchedule = props.secret.addRotationSchedule('RotationSchedule', { rotationLambda, - automaticallyAfterDays: props.automaticallyAfterDays + automaticallyAfter: props.automaticallyAfter }); rotationSchedule.node.addDependency(permission); // Cannot rotate without permission } diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 86296ad4564d4..4efa048af0618 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -100,8 +100,16 @@ }, "awslint": { "exclude": [ - "construct-base-is-private:@aws-cdk/aws-rds.DatabaseInstanceBase" + "construct-base-is-private:@aws-cdk/aws-rds.DatabaseInstanceBase", + "props-physical-name:@aws-cdk/aws-rds.ParameterGroupProps", + "props-physical-name:@aws-cdk/aws-rds.ClusterParameterGroupProps", + "props-physical-name:@aws-cdk/aws-rds.DatabaseClusterProps", + "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceProps", + "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceFromSnapshotProps", + "props-physical-name:@aws-cdk/aws-rds.DatabaseInstanceReadReplicaProps", + "props-physical-name:@aws-cdk/aws-rds.DatabaseSecretProps", + "props-physical-name:@aws-cdk/aws-rds.OptionGroupProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts index f9713e93f2f68..82981fc8b0810 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts @@ -14,7 +14,7 @@ const cluster = new rds.DatabaseCluster(stack, 'Database', { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 50e0ea3c51f72..5018ead694ccc 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -26,8 +26,8 @@ const cluster = new DatabaseCluster(stack, 'Database', { password: SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, vpc }, parameterGroup: params, diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts index 463dbc41f0685..4c739ecb7cbc6 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.ts @@ -45,16 +45,16 @@ class DatabaseInstanceStack extends cdk.Stack { // Database instance with production values const instance = new rds.DatabaseInstance(this, 'Instance', { engine: rds.DatabaseInstanceEngine.OracleSE1, - licenseModel: rds.LicenseModel.BringYourOwnLicense, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Medium), + licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', vpc, databaseName: 'ORCL', storageEncrypted: true, - backupRetentionPeriod: 7, - monitoringInterval: 60, + backupRetention: cdk.Duration.days(7), + monitoringInterval: cdk.Duration.seconds(60), enablePerformanceInsights: true, cloudwatchLogsExports: [ 'trace', @@ -62,7 +62,7 @@ class DatabaseInstanceStack extends cdk.Stack { 'alert', 'listener' ], - cloudwatchLogsRetention: logs.RetentionDays.OneMonth, + cloudwatchLogsRetention: logs.RetentionDays.ONE_MONTH, autoMinorVersionUpgrade: false, optionGroup, parameterGroup diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index a56b720228e54..ca0832be5501a 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -20,7 +20,7 @@ export = { password: SecretValue.plainText('tooshort'), }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); @@ -59,7 +59,7 @@ export = { password: SecretValue.plainText('tooshort'), }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); @@ -93,7 +93,7 @@ export = { password: SecretValue.plainText('tooshort'), }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc, securityGroup: sg } @@ -131,7 +131,7 @@ export = { password: SecretValue.plainText('tooshort'), }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc }, parameterGroup: group @@ -157,7 +157,7 @@ export = { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); @@ -214,7 +214,7 @@ export = { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc }, kmsKey: new kms.Key(stack, 'Key') @@ -251,7 +251,7 @@ export = { username: 'admin', }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), parameterGroup, vpc } @@ -280,7 +280,7 @@ export = { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc }, }); @@ -307,7 +307,7 @@ export = { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc }, }); @@ -318,6 +318,32 @@ export = { EngineVersion: "10.7", })); + test.done(); + }, + + 'cluster exposes different read and write endpoints'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const cluster = new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.Aurora, + masterUser: { + username: 'admin', + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc + } + }); + + // THEN + test.notDeepEqual( + stack.resolve(cluster.clusterEndpoint), + stack.resolve(cluster.clusterReadEndpoint) + ); + test.done(); } }; diff --git a/packages/@aws-cdk/aws-rds/test/test.instance.ts b/packages/@aws-cdk/aws-rds/test/test.instance.ts index 0567d2f641744..7c70ad0b21423 100644 --- a/packages/@aws-cdk/aws-rds/test/test.instance.ts +++ b/packages/@aws-cdk/aws-rds/test/test.instance.ts @@ -16,16 +16,16 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.OracleSE1, - licenseModel: rds.LicenseModel.BringYourOwnLicense, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Medium), + licenseModel: rds.LicenseModel.BRING_YOUR_OWN_LICENSE, + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.MEDIUM), multiAz: true, storageType: rds.StorageType.IO1, masterUsername: 'syscdk', vpc, databaseName: 'ORCL', storageEncrypted: true, - backupRetentionPeriod: 7, - monitoringInterval: 60, + backupRetention: cdk.Duration.days(7), + monitoringInterval: cdk.Duration.minutes(1), enablePerformanceInsights: true, cloudwatchLogsExports: [ 'trace', @@ -33,7 +33,7 @@ export = { 'alert', 'listener' ], - cloudwatchLogsRetention: logs.RetentionDays.OneMonth, + cloudwatchLogsRetention: logs.RetentionDays.ONE_MONTH, autoMinorVersionUpgrade: false, }); @@ -208,7 +208,7 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SqlServerEE, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: cdk.SecretValue.plainText('tooshort'), vpc, @@ -237,7 +237,7 @@ export = { new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.Postgres, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc }); @@ -257,7 +257,7 @@ export = { test.throws(() => new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, generateMasterUserPassword: true, }), /`masterUsername`.*`generateMasterUserPassword`/); @@ -271,7 +271,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const sourceInstance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc }); @@ -280,7 +280,7 @@ export = { new rds.DatabaseInstanceReadReplica(stack, 'ReadReplica', { sourceDatabaseInstance: sourceInstance, engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Large), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc }); @@ -300,7 +300,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc }); @@ -367,7 +367,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc }); @@ -420,7 +420,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc }); @@ -430,7 +430,7 @@ export = { dimensions: { DBInstanceIdentifier: { Ref: 'InstanceC1063A87' } }, namespace: 'AWS/RDS', metricName: 'CPUUtilization', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Average' }); @@ -445,7 +445,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc }); @@ -476,10 +476,10 @@ export = { // WHEN new rds.DatabaseInstance(stack, 'Instance', { engine: rds.DatabaseInstanceEngine.Mysql, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'admin', vpc, - backupRetentionPeriod: 0, + backupRetention: cdk.Duration.seconds(0), }); // THEN diff --git a/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts index ecf4e761c2b10..250c4535a183b 100644 --- a/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts +++ b/packages/@aws-cdk/aws-rds/test/test.secret-rotation.ts @@ -20,7 +20,7 @@ export = { username: 'admin' }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); @@ -168,7 +168,7 @@ export = { password: SecretValue.plainText('tooshort') }, instanceProps: { - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), vpc } }); @@ -210,7 +210,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.MariaDb, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', vpc }); @@ -353,7 +353,7 @@ export = { // WHEN const instance = new rds.DatabaseInstance(stack, 'Database', { engine: rds.DatabaseInstanceEngine.SqlServerEE, - instanceClass: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + instanceClass: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', masterUserPassword: SecretValue.plainText('tooshort'), vpc diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index 87d2f346b103e..bba51a08e06f4 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -1,5 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); -import { Construct, ContextProvider, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, ContextProvider, Duration, Lazy, Resource, Stack } from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); import { HostedZoneProviderProps } from './hosted-zone-provider'; import { HostedZoneAttributes, IHostedZone } from './hosted-zone-ref'; @@ -121,7 +121,7 @@ export class HostedZone extends Resource implements IHostedZone { vpcs: Lazy.anyValue({ produce: () => this.vpcs.length === 0 ? undefined : this.vpcs }) }); - this.hostedZoneId = resource.refAsString; + this.hostedZoneId = resource.ref; this.hostedZoneNameServers = resource.attrNameServers; this.zoneName = props.zoneName; @@ -136,7 +136,7 @@ export class HostedZone extends Resource implements IHostedZone { * @param vpc the other VPC to add. */ public addVpc(vpc: ec2.IVpc) { - this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: vpc.region }); + this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: Stack.of(vpc).region }); } } @@ -217,7 +217,7 @@ export interface ZoneDelegationOptions { * * @default 172800 */ - readonly ttl?: number; + readonly ttl?: Duration; } export interface PrivateHostedZoneProps extends CommonHostedZoneProps { diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 1d178c6abc69d..8024f8d5f28cb 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, Resource, Token } from '@aws-cdk/cdk'; import { IAliasRecordTarget } from './alias-record-target'; import { IHostedZone } from './hosted-zone-ref'; import { CfnRecordSet } from './route53.generated'; @@ -49,11 +49,11 @@ export interface RecordSetOptions { readonly recordName?: string; /** - * The resource record cache time to live (TTL) in seconds. + * The resource record cache time to live (TTL). * - * @default 1800 seconds + * @default Duration.minutes(30) */ - readonly ttl?: number; + readonly ttl?: Duration; /** * A comment to add on the record. @@ -110,7 +110,7 @@ export class RecordSet extends Resource implements IRecordSet { constructor(scope: Construct, id: string, props: RecordSetProps) { super(scope, id); - const ttl = props.target.aliasTarget ? undefined : (props.ttl || 1800).toString(); + const ttl = props.target.aliasTarget ? undefined : ((props.ttl && props.ttl.toSeconds()) || 1800).toString(); const recordSet = new CfnRecordSet(this, 'Resource', { hostedZoneId: props.zone.hostedZoneId, @@ -122,7 +122,7 @@ export class RecordSet extends Resource implements IRecordSet { comment: props.comment }); - this.domainName = recordSet.refAsString; + this.domainName = recordSet.ref; } } @@ -446,7 +446,7 @@ export class ZoneDelegationRecord extends RecordSet { ? props.nameServers // Can't map a string-array token! : props.nameServers.map(ns => (Token.isUnresolved(ns) || ns.endsWith('.')) ? ns : `${ns}.`) ), - ttl: props.ttl || 172_800 + ttl: props.ttl || Duration.days(2) }); } } diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index d9fe4af2f7d7e..e704f96810491 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -89,7 +89,20 @@ "awslint": { "exclude": [ "from-attributes:fromPrivateHostedZoneAttributes", - "from-attributes:fromPublicHostedZoneAttributes" + "from-attributes:fromPublicHostedZoneAttributes", + "props-physical-name:@aws-cdk/aws-route53.ZoneDelegationRecordProps", + "props-physical-name:@aws-cdk/aws-route53.ARecordProps", + "props-physical-name:@aws-cdk/aws-route53.CaaAmazonRecordProps", + "props-physical-name:@aws-cdk/aws-route53.CaaRecordProps", + "props-physical-name:@aws-cdk/aws-route53.CnameRecordProps", + "props-physical-name:@aws-cdk/aws-route53.HostedZoneProps", + "props-physical-name:@aws-cdk/aws-route53.MxRecordProps", + "props-physical-name:@aws-cdk/aws-route53.PrivateHostedZoneProps", + "props-physical-name:@aws-cdk/aws-route53.PublicHostedZoneProps", + "props-physical-name:@aws-cdk/aws-route53.RecordSetProps", + "props-physical-name:@aws-cdk/aws-route53.SrvRecordProps", + "props-physical-name:@aws-cdk/aws-route53.TxtRecordProps", + "props-physical-name:@aws-cdk/aws-route53.AaaaRecordProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-route53/test/integ.route53.ts b/packages/@aws-cdk/aws-route53/test/integ.route53.ts index 544c84e09b80c..f50521f546257 100644 --- a/packages/@aws-cdk/aws-route53/test/integ.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/integ.route53.ts @@ -27,7 +27,7 @@ new TxtRecord(privateZone, 'TXT', { 'Bar!', 'Baz?' ], - ttl: 60 + ttl: cdk.Duration.minutes(1) }); new CnameRecord(stack, 'CNAME', { diff --git a/packages/@aws-cdk/aws-route53/test/test.record-set.ts b/packages/@aws-cdk/aws-route53/test/test.record-set.ts index 8dca376245ab5..e455961379c72 100644 --- a/packages/@aws-cdk/aws-route53/test/test.record-set.ts +++ b/packages/@aws-cdk/aws-route53/test/test.record-set.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import route53 = require('../lib'); @@ -49,7 +49,7 @@ export = { recordName: 'aa', recordType: route53.RecordType.CNAME, target: route53.RecordTarget.fromValues('bbb'), - ttl: 6077 + ttl: Duration.seconds(6077) }); // THEN diff --git a/packages/@aws-cdk/aws-route53/test/test.route53.ts b/packages/@aws-cdk/aws-route53/test/test.route53.ts index e5cac2f6a1879..1eb06a4e82556 100644 --- a/packages/@aws-cdk/aws-route53/test/test.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/test.route53.ts @@ -182,7 +182,7 @@ export = { const delegate = new PublicHostedZone(stack, 'SubZone', { zoneName: 'sub.top.test' }); // WHEN - zone.addDelegation(delegate, { ttl: 1337 }); + zone.addDelegation(delegate, { ttl: cdk.Duration.seconds(1337) }); // THEN expect(stack).to(haveResource('AWS::Route53::RecordSet', { diff --git a/packages/@aws-cdk/aws-s3-assets/package-lock.json b/packages/@aws-cdk/aws-s3-assets/package-lock.json index 7c14726c9ec0f..ccc03d6cf433b 100644 --- a/packages/@aws-cdk/aws-s3-assets/package-lock.json +++ b/packages/@aws-cdk/aws-s3-assets/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3-assets", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index 4514758cbe124..f6c13cfc3fd70 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -49,7 +49,7 @@ export class BucketDeployment extends cdk.Construct { runtime: lambda.Runtime.Python36, handler: 'index.handler', lambdaPurpose: 'Custom::CDKBucketDeployment', - timeout: 15 * 60 + timeout: cdk.Duration.minutes(15) }); const source = props.source.bind(this); diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index 27ef63893f0fe..d308a24dd1056 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -26,7 +26,7 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { const permission = this.fn.node.findChild(permissionId) as CfnResource; return { - type: s3.BucketNotificationDestinationType.Lambda, + type: s3.BucketNotificationDestinationType.LAMBDA, arn: this.fn.functionArn, dependencies: permission ? [ permission ] : undefined }; diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/sns.ts b/packages/@aws-cdk/aws-s3-notifications/lib/sns.ts index 81a5bce07234b..a49a15de14ded 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/sns.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/sns.ts @@ -22,7 +22,7 @@ export class SnsDestination implements s3.IBucketNotificationDestination { return { arn: this.topic.topicArn, - type: s3.BucketNotificationDestinationType.Topic, + type: s3.BucketNotificationDestinationType.TOPIC, dependencies: [ this.topic ] // make sure the topic policy resource is created before the notification config }; } diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/sqs.ts b/packages/@aws-cdk/aws-s3-notifications/lib/sqs.ts index 19de8e6ab7db7..48b2a83c0c360 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/sqs.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/sqs.ts @@ -33,7 +33,7 @@ export class SqsDestination implements s3.IBucketNotificationDestination { return { arn: this.queue.queueArn, - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, dependencies: [ this.queue ] }; } diff --git a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts index 8b6199d400c0d..c22c94adb1461 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/integ.notifications.ts @@ -14,8 +14,8 @@ const bucket = new s3.Bucket(stack, 'Bucket', { const topic = new sns.Topic(stack, 'Topic'); const topic3 = new sns.Topic(stack, 'Topic3'); -bucket.addEventNotification(s3.EventType.ObjectCreatedPut, new s3n.SnsDestination(topic)); -bucket.addEventNotification(s3.EventType.ObjectRemoved, new s3n.SnsDestination(topic3), { prefix: 'home/myusername/' }); +bucket.addEventNotification(s3.EventType.OBJECT_CREATED_PUT, new s3n.SnsDestination(topic)); +bucket.addEventNotification(s3.EventType.OBJECT_REMOVED, new s3n.SnsDestination(topic3), { prefix: 'home/myusername/' }); const bucket2 = new s3.Bucket(stack, 'Bucket2', { removalPolicy: cdk.RemovalPolicy.Destroy diff --git a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts index 90e8a1d8be2e5..6579cd7c9822d 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/lambda/integ.bucket-notifications.ts @@ -22,7 +22,7 @@ const bucketB = new s3.Bucket(stack, 'YourBucket', { }); bucketA.addObjectCreatedNotification(new s3n.LambdaDestination(fn), { suffix: '.png' }); -bucketB.addEventNotification(s3.EventType.ObjectRemoved, new s3n.LambdaDestination(fn)); +bucketB.addEventNotification(s3.EventType.OBJECT_REMOVED, new s3n.LambdaDestination(fn)); app.synth(); diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index 9dd6b4c793305..ddfcec88f7206 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -30,7 +30,7 @@ test('when notification are added, a custom resource is provisioned + a lambda h const bucket = new s3.Bucket(stack, 'MyBucket'); const topic = new sns.Topic(stack, 'MyTopic'); - bucket.addEventNotification(s3.EventType.ObjectCreated, new s3n.SnsDestination(topic)); + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); expect(stack).toHaveResource('AWS::S3::Bucket'); expect(stack).toHaveResource('AWS::Lambda::Function', { Description: 'AWS CloudFormation handler for "Custom::S3BucketNotifications" resources (@aws-cdk/aws-s3)' }); @@ -45,7 +45,7 @@ test('when notification are added, you can tag the lambda', () => { const topic = new sns.Topic(stack, 'MyTopic'); - bucket.addEventNotification(s3.EventType.ObjectCreated, new s3n.SnsDestination(topic)); + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic)); expect(stack).toHaveResource('AWS::S3::Bucket'); expect(stack).toHaveResource('AWS::Lambda::Function', { @@ -106,27 +106,27 @@ test('subscription types', () => { const queueTarget: s3.IBucketNotificationDestination = { bind: _ => ({ - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, arn: 'arn:aws:sqs:...' }) }; const lambdaTarget: s3.IBucketNotificationDestination = { bind: _ => ({ - type: s3.BucketNotificationDestinationType.Lambda, + type: s3.BucketNotificationDestinationType.LAMBDA, arn: 'arn:aws:lambda:...' }) }; const topicTarget: s3.IBucketNotificationDestination = { bind: _ => ({ - type: s3.BucketNotificationDestinationType.Topic, + type: s3.BucketNotificationDestinationType.TOPIC, arn: 'arn:aws:sns:...' }) }; - bucket.addEventNotification(s3.EventType.ObjectCreated, queueTarget); - bucket.addEventNotification(s3.EventType.ObjectCreated, lambdaTarget); + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, queueTarget); + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, lambdaTarget); bucket.addObjectRemovedNotification(topicTarget, { prefix: 'prefix' }); expect(stack).toHaveResource('Custom::S3BucketNotifications', { @@ -183,16 +183,16 @@ test('multiple subscriptions of the same type', () => { const bucket = new s3.Bucket(stack, 'TestBucket'); - bucket.addEventNotification(s3.EventType.ObjectRemovedDelete, { + bucket.addEventNotification(s3.EventType.OBJECT_REMOVED_DELETE, { bind: _ => ({ - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, arn: 'arn:aws:sqs:...:queue1' }) }); - bucket.addEventNotification(s3.EventType.ObjectRemovedDelete, { + bucket.addEventNotification(s3.EventType.OBJECT_REMOVED_DELETE, { bind: _ => ({ - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, arn: 'arn:aws:sqs:...:queue2' }) }); @@ -232,11 +232,11 @@ test('prefix/suffix filters', () => { const bucket = new s3.Bucket(stack, 'TestBucket'); const bucketNotificationTarget = { - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, arn: 'arn:aws:sqs:...' }; - bucket.addEventNotification(s3.EventType.ObjectRemovedDelete, { bind: _ => bucketNotificationTarget }, { prefix: 'images/', suffix: '.jpg' }); + bucket.addEventNotification(s3.EventType.OBJECT_REMOVED_DELETE, { bind: _ => bucketNotificationTarget }, { prefix: 'images/', suffix: '.jpg' }); expect(stack).toHaveResource('Custom::S3BucketNotifications', { "ServiceToken": { @@ -283,7 +283,7 @@ test('a notification destination can specify a set of dependencies that must be const dest: s3.IBucketNotificationDestination = { bind: () => ({ arn: 'arn', - type: s3.BucketNotificationDestinationType.Queue, + type: s3.BucketNotificationDestinationType.QUEUE, dependencies: [ dependent ] }) }; diff --git a/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts index 977cc00c5f7fb..24a6b4094e5d5 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts @@ -75,7 +75,7 @@ test('queues can be used as destinations', () => { test('if the queue is encrypted with a custom kms key, the key resource policy is updated to allow s3 to read messages', () => { const stack = new Stack(); const bucket = new s3.Bucket(stack, 'Bucket'); - const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.Kms }); + const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS }); bucket.addObjectCreatedNotification(new notif.SqsDestination(queue)); @@ -142,7 +142,7 @@ test('if the queue is encrypted with a custom kms key, the key resource policy i test('fails if trying to subscribe to a queue with managed kms encryption', () => { const stack = new Stack(); - const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KmsManaged }); + const queue = new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS_MANAGED }); const bucket = new s3.Bucket(stack, 'Bucket'); expect(() => { bucket.addObjectRemovedNotification(new notif.SqsDestination(queue)); diff --git a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.ts b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.ts index ce0dec65408bd..257d84c99bd65 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/sqs/integ.bucket-notifications.ts @@ -19,7 +19,7 @@ const bucket2 = new s3.Bucket(stack, 'Bucket2', { }); bucket2.addObjectCreatedNotification(new s3n.SqsDestination(queue), { suffix: '.png' }); -const encryptedQueue = new sqs.Queue(stack, 'EncryptedQueue', { encryption: sqs.QueueEncryption.Kms }); +const encryptedQueue = new sqs.Queue(stack, 'EncryptedQueue', { encryption: sqs.QueueEncryption.KMS }); bucket1.addObjectRemovedNotification(new s3n.SqsDestination(encryptedQueue)); app.synth(); diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 79ad4318fe811..bd6e42e7b7815 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -2,7 +2,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import { Construct, IResource, Lazy, PhysicalName, - RemovalPolicy, Resource, ResourceIdentifiers, Stack, Token } from '@aws-cdk/cdk'; + RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { EOL } from 'os'; import { BucketPolicy } from './bucket-policy'; import { IBucketNotificationDestination } from './destination'; @@ -889,12 +889,11 @@ export class Bucket extends BucketBase { }); const { bucketEncryption, encryptionKey } = this.parseEncryption(props); - if (props.bucketName && !Token.isUnresolved(props.bucketName)) { - this.validateBucketName(props.bucketName); - } + + this.validateBucketName(this.physicalName); const resource = new CfnBucket(this, 'Resource', { - bucketName: this.physicalName.value, + bucketName: this.physicalName, bucketEncryption, versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined, lifecycleConfiguration: Lazy.anyValue({ produce: () => this.parseLifecycleConfiguration() }), @@ -909,14 +908,14 @@ export class Bucket extends BucketBase { this.versioned = props.versioned; this.encryptionKey = encryptionKey; - const resourceIdentifiers = new ResourceIdentifiers(this, { + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ arn: resource.attrArn, - name: resource.refAsString, + name: resource.ref, arnComponents: { region: '', account: '', service: 's3', - resource: this.physicalName.value || '', + resource: this.physicalName, }, }); this.bucketArn = resourceIdentifiers.arn; @@ -951,7 +950,7 @@ export class Bucket extends BucketBase { * @param rule The rule to add */ public addLifecycleRule(rule: LifecycleRule) { - if ((rule.noncurrentVersionExpirationInDays !== undefined + if ((rule.noncurrentVersionExpiration !== undefined || (rule.noncurrentVersionTransitions && rule.noncurrentVersionTransitions.length > 0)) && !this.versioned) { throw new Error("Cannot use 'noncurrent' rules on a nonversioned bucket"); @@ -1010,7 +1009,7 @@ export class Bucket extends BucketBase { * @param filters Filters (see onEvent) */ public addObjectCreatedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { - return this.addEventNotification(EventType.ObjectCreated, dest, ...filters); + return this.addEventNotification(EventType.OBJECT_CREATED, dest, ...filters); } /** @@ -1022,11 +1021,11 @@ export class Bucket extends BucketBase { * @param filters Filters (see onEvent) */ public addObjectRemovedNotification(dest: IBucketNotificationDestination, ...filters: NotificationKeyFilter[]) { - return this.addEventNotification(EventType.ObjectRemoved, dest, ...filters); + return this.addEventNotification(EventType.OBJECT_REMOVED, dest, ...filters); } - private validateBucketName(physicalName: PhysicalName): void { - const bucketName = physicalName.value; + private validateBucketName(physicalName: string): void { + const bucketName = physicalName; if (!bucketName || Token.isUnresolved(bucketName)) { // the name is a late-bound value, not a defined string, // so skip validation @@ -1078,19 +1077,19 @@ export class Bucket extends BucketBase { // default based on whether encryptionKey is specified let encryptionType = props.encryption; if (encryptionType === undefined) { - encryptionType = props.encryptionKey ? BucketEncryption.Kms : BucketEncryption.Unencrypted; + encryptionType = props.encryptionKey ? BucketEncryption.KMS : BucketEncryption.UNENCRYPTED; } // if encryption key is set, encryption must be set to KMS. - if (encryptionType !== BucketEncryption.Kms && props.encryptionKey) { + if (encryptionType !== BucketEncryption.KMS && props.encryptionKey) { throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType})`); } - if (encryptionType === BucketEncryption.Unencrypted) { + if (encryptionType === BucketEncryption.UNENCRYPTED) { return { bucketEncryption: undefined, encryptionKey: undefined }; } - if (encryptionType === BucketEncryption.Kms) { + if (encryptionType === BucketEncryption.KMS) { const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', { description: `Created by ${this.node.path}` }); @@ -1108,7 +1107,7 @@ export class Bucket extends BucketBase { return { encryptionKey, bucketEncryption }; } - if (encryptionType === BucketEncryption.S3Managed) { + if (encryptionType === BucketEncryption.S3_MANAGED) { const bucketEncryption = { serverSideEncryptionConfiguration: [ { serverSideEncryptionByDefault: { sseAlgorithm: 'AES256' } } @@ -1118,7 +1117,7 @@ export class Bucket extends BucketBase { return { bucketEncryption }; } - if (encryptionType === BucketEncryption.KmsManaged) { + if (encryptionType === BucketEncryption.KMS_MANAGED) { const bucketEncryption = { serverSideEncryptionConfiguration: [ { serverSideEncryptionByDefault: { sseAlgorithm: 'aws:kms' } } @@ -1148,21 +1147,21 @@ export class Bucket extends BucketBase { const x: CfnBucket.RuleProperty = { // tslint:disable-next-line:max-line-length - abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfterDays !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfterDays } : undefined, + abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfter !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfter.toDays() } : undefined, expirationDate: rule.expirationDate, - expirationInDays: rule.expirationInDays, + expirationInDays: rule.expiration && rule.expiration.toDays(), id: rule.id, - noncurrentVersionExpirationInDays: rule.noncurrentVersionExpirationInDays, + noncurrentVersionExpirationInDays: rule.noncurrentVersionExpiration && rule.noncurrentVersionExpiration.toDays(), noncurrentVersionTransitions: mapOrUndefined(rule.noncurrentVersionTransitions, t => ({ storageClass: t.storageClass.value, - transitionInDays: t.transitionInDays + transitionInDays: t.transitionAfter.toDays() })), prefix: rule.prefix, status: enabled ? 'Enabled' : 'Disabled', transitions: mapOrUndefined(rule.transitions, t => ({ storageClass: t.storageClass.value, transitionDate: t.transitionDate, - transitionInDays: t.transitionInDays + transitionInDays: t.transitionAfter && t.transitionAfter.toDays() })), tagFilters: self.parseTagFilters(rule.tagFilters) }; @@ -1242,23 +1241,23 @@ export enum BucketEncryption { /** * Objects in the bucket are not encrypted. */ - Unencrypted = 'NONE', + UNENCRYPTED = 'NONE', /** * Server-side KMS encryption with a master key managed by KMS. */ - KmsManaged = 'MANAGED', + KMS_MANAGED = 'MANAGED', /** * Server-side encryption with a master key managed by S3. */ - S3Managed = 'S3MANAGED', + S3_MANAGED = 'S3MANAGED', /** * Server-side encryption with a KMS key managed by the user. * If `encryptionKey` is specified, this key will be used, otherwise, one will be defined. */ - Kms = 'KMS', + KMS = 'KMS', } /** @@ -1272,7 +1271,7 @@ export enum EventType { * request notification regardless of the API that was used to create an * object. */ - ObjectCreated = 's3:ObjectCreated:*', + OBJECT_CREATED = 's3:ObjectCreated:*', /** * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using @@ -1281,7 +1280,7 @@ export enum EventType { * request notification regardless of the API that was used to create an * object. */ - ObjectCreatedPut = 's3:ObjectCreated:Put', + OBJECT_CREATED_PUT = 's3:ObjectCreated:Put', /** * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using @@ -1290,7 +1289,7 @@ export enum EventType { * request notification regardless of the API that was used to create an * object. */ - ObjectCreatedPost = 's3:ObjectCreated:Post', + OBJECT_CREATED_POST = 's3:ObjectCreated:Post', /** * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using @@ -1299,7 +1298,7 @@ export enum EventType { * request notification regardless of the API that was used to create an * object. */ - ObjectCreatedCopy = 's3:ObjectCreated:Copy', + OBJECT_CREATED_COPY = 's3:ObjectCreated:Copy', /** * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using @@ -1308,7 +1307,7 @@ export enum EventType { * request notification regardless of the API that was used to create an * object. */ - ObjectCreatedCompleteMultipartUpload = 's3:ObjectCreated:CompleteMultipartUpload', + OBJECT_CREATED_COMPLETE_MULTIPART_UPLOAD = 's3:ObjectCreated:CompleteMultipartUpload', /** * By using the ObjectRemoved event types, you can enable notification when @@ -1325,7 +1324,7 @@ export enum EventType { * You will not receive event notifications from automatic deletes from * lifecycle policies or from failed operations. */ - ObjectRemoved = 's3:ObjectRemoved:*', + OBJECT_REMOVED = 's3:ObjectRemoved:*', /** * By using the ObjectRemoved event types, you can enable notification when @@ -1342,7 +1341,7 @@ export enum EventType { * You will not receive event notifications from automatic deletes from * lifecycle policies or from failed operations. */ - ObjectRemovedDelete = 's3:ObjectRemoved:Delete', + OBJECT_REMOVED_DELETE = 's3:ObjectRemoved:Delete', /** * By using the ObjectRemoved event types, you can enable notification when @@ -1359,14 +1358,14 @@ export enum EventType { * You will not receive event notifications from automatic deletes from * lifecycle policies or from failed operations. */ - ObjectRemovedDeleteMarkerCreated = 's3:ObjectRemoved:DeleteMarkerCreated', + OBJECT_REMOVED_DELETE_MARKER_CREATED = 's3:ObjectRemoved:DeleteMarkerCreated', /** * You can use this event type to request Amazon S3 to send a notification * message when Amazon S3 detects that an object of the RRS storage class is * lost. */ - ReducedRedundancyLostObject = 's3:ReducedRedundancyLostObject', + REDUCED_REDUNDANCY_LOST_OBJECT = 's3:ReducedRedundancyLostObject', } export interface NotificationKeyFilter { diff --git a/packages/@aws-cdk/aws-s3/lib/destination.ts b/packages/@aws-cdk/aws-s3/lib/destination.ts index 8a2c16c54608a..39e1f1b00a33c 100644 --- a/packages/@aws-cdk/aws-s3/lib/destination.ts +++ b/packages/@aws-cdk/aws-s3/lib/destination.ts @@ -40,7 +40,7 @@ export interface BucketNotificationDestinationConfig { * Supported types of notification destinations. */ export enum BucketNotificationDestinationType { - Lambda, - Queue, - Topic + LAMBDA, + QUEUE, + TOPIC } diff --git a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts index 733df4e6f0956..ae1b09863a148 100644 --- a/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts +++ b/packages/@aws-cdk/aws-s3/lib/notifications-resource/notifications-resource.ts @@ -68,15 +68,15 @@ export class BucketNotifications extends cdk.Construct { // based on the target type, add the the correct configurations array switch (targetProps.type) { - case BucketNotificationDestinationType.Lambda: + case BucketNotificationDestinationType.LAMBDA: this.lambdaNotifications.push({ ...commonConfig, LambdaFunctionArn: targetProps.arn }); break; - case BucketNotificationDestinationType.Queue: + case BucketNotificationDestinationType.QUEUE: this.queueNotifications.push({ ...commonConfig, QueueArn: targetProps.arn }); break; - case BucketNotificationDestinationType.Topic: + case BucketNotificationDestinationType.TOPIC: this.topicNotifications.push({ ...commonConfig, TopicArn: targetProps.arn }); break; diff --git a/packages/@aws-cdk/aws-s3/lib/rule.ts b/packages/@aws-cdk/aws-s3/lib/rule.ts index ea20482e63ee9..e2f976e264e7a 100644 --- a/packages/@aws-cdk/aws-s3/lib/rule.ts +++ b/packages/@aws-cdk/aws-s3/lib/rule.ts @@ -1,3 +1,5 @@ +import { Duration } from "@aws-cdk/cdk"; + /** * Declaration of a Life cycle rule */ @@ -24,7 +26,7 @@ export interface LifecycleRule { * * @default Incomplete uploads are never aborted */ - readonly abortIncompleteMultipartUploadAfterDays?: number; + readonly abortIncompleteMultipartUploadAfter?: Duration; /** * Indicates when objects are deleted from Amazon S3 and Amazon Glacier. @@ -48,7 +50,7 @@ export interface LifecycleRule { * * @default No expiration timeout */ - readonly expirationInDays?: number; + readonly expiration?: Duration; /** * Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire. @@ -62,7 +64,7 @@ export interface LifecycleRule { * * @default No noncurrent version expiration */ - readonly noncurrentVersionExpirationInDays?: number; + readonly noncurrentVersionExpiration?: Duration; /** * One or more transition rules that specify when non-current objects transition to a specified storage class. @@ -123,7 +125,7 @@ export interface Transition { * * @default No transition count. */ - readonly transitionInDays?: number; + readonly transitionAfter?: Duration; } /** @@ -140,7 +142,7 @@ export interface NoncurrentVersionTransition { * * @default No transition count. */ - readonly transitionInDays: number; + readonly transitionAfter: Duration; } /** diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 24a51929c5e0d..6d8c11a0da34d 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -85,5 +85,10 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-s3.BucketPolicyProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3/test/integ.bucket.ts b/packages/@aws-cdk/aws-s3/test/integ.bucket.ts index 5d88247867de3..48e239e4bdc0d 100644 --- a/packages/@aws-cdk/aws-s3/test/integ.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/integ.bucket.ts @@ -8,12 +8,12 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-s3'); const bucket = new s3.Bucket(stack, 'MyBucket', { - encryption: s3.BucketEncryption.Kms, + encryption: s3.BucketEncryption.KMS, removalPolicy: cdk.RemovalPolicy.Destroy }); const otherwiseEncryptedBucket = new s3.Bucket(stack, 'MyOtherBucket', { - encryption: s3.BucketEncryption.S3Managed, + encryption: s3.BucketEncryption.S3_MANAGED, removalPolicy: cdk.RemovalPolicy.Destroy }); diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 012c3e169b701..dd78c0650e9f3 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -45,7 +45,7 @@ export = { 'bucket without encryption'(test: Test) { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { - encryption: s3.BucketEncryption.Unencrypted + encryption: s3.BucketEncryption.UNENCRYPTED }); expect(stack).toMatch({ @@ -63,7 +63,7 @@ export = { 'bucket with managed encryption'(test: Test) { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { - encryption: s3.BucketEncryption.KmsManaged + encryption: s3.BucketEncryption.KMS_MANAGED }); expect(stack).toMatch({ @@ -229,7 +229,7 @@ export = { const myKey = new kms.Key(stack, 'MyKey'); test.throws(() => new s3.Bucket(stack, 'MyBucket', { - encryption: s3.BucketEncryption.KmsManaged, + encryption: s3.BucketEncryption.KMS_MANAGED, encryptionKey: myKey }), /encryptionKey is specified, so 'encryption' must be set to KMS/); @@ -241,7 +241,7 @@ export = { const myKey = new kms.Key(stack, 'MyKey'); test.throws(() => new s3.Bucket(stack, 'MyBucket', { - encryption: s3.BucketEncryption.Unencrypted, + encryption: s3.BucketEncryption.UNENCRYPTED, encryptionKey: myKey }), /encryptionKey is specified, so 'encryption' must be set to KMS/); @@ -253,7 +253,7 @@ export = { const encryptionKey = new kms.Key(stack, 'MyKey', { description: 'hello, world' }); - new s3.Bucket(stack, 'MyBucket', { encryptionKey, encryption: s3.BucketEncryption.Kms }); + new s3.Bucket(stack, 'MyBucket', { encryptionKey, encryption: s3.BucketEncryption.KMS }); expect(stack).toMatch({ "Resources": { @@ -427,7 +427,7 @@ export = { 'addPermission creates a bucket policy'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Unencrypted }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); bucket.addToResourcePolicy(new iam.PolicyStatement({ resources: ['foo'], actions: [ 'bar' ]})); @@ -464,7 +464,7 @@ export = { 'forBucket returns a permission statement associated with the bucket\'s ARN'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Unencrypted }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); const x = new iam.PolicyStatement({ resources: [bucket.bucketArn], actions: ['s3:ListBucket'] }); @@ -480,7 +480,7 @@ export = { 'arnForObjects returns a permission statement associated with objects in the bucket'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Unencrypted }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); const p = new iam.PolicyStatement({ resources: [bucket.arnForObjects('hello/world')], actions: ['s3:GetObject'] }); @@ -502,7 +502,7 @@ export = { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Unencrypted }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.UNENCRYPTED }); const user = new iam.User(stack, 'MyUser'); const team = new iam.Group(stack, 'MyTeam'); @@ -536,7 +536,7 @@ export = { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.Retain, - encryption: s3.BucketEncryption.Unencrypted + encryption: s3.BucketEncryption.UNENCRYPTED }); expect(stack).toMatch({ @@ -765,7 +765,7 @@ export = { 'grant permissions to non-identity principal'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); // WHEN bucket.grantRead(new iam.OrganizationPrincipal('o-1234')); @@ -823,7 +823,7 @@ export = { 'if an encryption key is included, encrypt/decrypt permissions are also added both ways'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); const user = new iam.User(stack, 'MyUser'); bucket.grantReadWrite(user); @@ -994,7 +994,7 @@ export = { 'more grants'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.KMS }); const putter = new iam.User(stack, 'Putter'); const writer = new iam.User(stack, 'Writer'); const deleter = new iam.User(stack, 'Deleter'); @@ -1192,7 +1192,7 @@ export = { const bucketFromStackA = new s3.Bucket(stackA, 'MyBucket', { bucketName: cdk.PhysicalName.of('my-bucket-physical-name'), encryptionKey: key, - encryption: s3.BucketEncryption.Kms, + encryption: s3.BucketEncryption.KMS, }); const stackB = new cdk.Stack(undefined, 'StackB', { env: { account: '234567890123' }}); diff --git a/packages/@aws-cdk/aws-s3/test/test.notification.ts b/packages/@aws-cdk/aws-s3/test/test.notification.ts index 5a34fbaec73ff..f11fac28ae752 100644 --- a/packages/@aws-cdk/aws-s3/test/test.notification.ts +++ b/packages/@aws-cdk/aws-s3/test/test.notification.ts @@ -9,10 +9,10 @@ export = { const bucket = new s3.Bucket(stack, 'MyBucket'); - bucket.addEventNotification(s3.EventType.ObjectCreated, { + bucket.addEventNotification(s3.EventType.OBJECT_CREATED, { bind: () => ({ arn: 'ARN', - type: s3.BucketNotificationDestinationType.Topic + type: s3.BucketNotificationDestinationType.TOPIC }) }); diff --git a/packages/@aws-cdk/aws-s3/test/test.rules.ts b/packages/@aws-cdk/aws-s3/test/test.rules.ts index 1724db63cd33f..d89064a556b1f 100644 --- a/packages/@aws-cdk/aws-s3/test/test.rules.ts +++ b/packages/@aws-cdk/aws-s3/test/test.rules.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Bucket, StorageClass } from '../lib'; @@ -11,7 +11,7 @@ export = { // WHEN new Bucket(stack, 'Bucket', { lifecycleRules: [{ - expirationInDays: 30 + expiration: Duration.days(30) }] }); @@ -35,7 +35,7 @@ export = { // WHEN const bucket = new Bucket(stack, 'Bucket'); bucket.addLifecycleRule({ - expirationInDays: 30 + expiration: Duration.days(30) }); // THEN @@ -84,7 +84,7 @@ export = { lifecycleRules: [{ transitions: [{ storageClass: StorageClass.Glacier, - transitionInDays: 30 + transitionAfter: Duration.days(30) }] }] }); @@ -113,7 +113,7 @@ export = { test.throws(() => { new Bucket(stack, 'Bucket1', { lifecycleRules: [{ - noncurrentVersionExpirationInDays: 10 + noncurrentVersionExpiration: Duration.days(10) }] }); }); @@ -122,7 +122,7 @@ export = { new Bucket(stack, 'Bucket2', { versioned: true, lifecycleRules: [{ - noncurrentVersionExpirationInDays: 10 + noncurrentVersionExpiration: Duration.days(10) }] }); diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts index f261707ba1dd8..d342817a861fc 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts @@ -1,5 +1,5 @@ import lambda = require('@aws-cdk/aws-lambda'); -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, Duration, Resource } from '@aws-cdk/cdk'; import { ISecret } from './secret'; import { CfnRotationSchedule } from './secretsmanager.generated'; @@ -16,9 +16,9 @@ export interface RotationScheduleOptions { * Specifies the number of days after the previous rotation before * Secrets Manager triggers the next automatic rotation. * - * @default 30 + * @default Duration.days(30) */ - readonly automaticallyAfterDays?: number; + readonly automaticallyAfter?: Duration; } /** @@ -42,7 +42,7 @@ export class RotationSchedule extends Resource { secretId: props.secret.secretArn, rotationLambdaArn: props.rotationLambda.functionArn, rotationRules: { - automaticallyAfterDays: props.automaticallyAfterDays || 30 + automaticallyAfterDays: props.automaticallyAfter && props.automaticallyAfter.toDays() || 30 } }); } diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index e44a1aa442d9d..b8c1c2661ddad 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Construct, IResource, Resource, SecretValue, Stack } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource, SecretValue, Stack } from '@aws-cdk/cdk'; import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule'; import secretsmanager = require('./secretsmanager.generated'); @@ -78,7 +78,7 @@ export interface SecretProps { * * @default - A name is generated by CloudFormation. */ - readonly name?: string; + readonly secretName?: PhysicalName; } /** @@ -173,7 +173,9 @@ export class Secret extends SecretBase { public readonly secretArn: string; constructor(scope: Construct, id: string, props: SecretProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.secretName, + }); if (props.generateSecretString && (props.generateSecretString.secretStringTemplate || props.generateSecretString.generateStringKey) && @@ -185,11 +187,22 @@ export class Secret extends SecretBase { description: props.description, kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, generateSecretString: props.generateSecretString || {}, - name: props.name, + name: this.physicalName, + }); + + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.ref, + name: this.physicalName, + arnComponents: { + service: 'secretsmanager', + resource: 'secret', + resourceName: this.physicalName, + sep: ':', + }, }); + this.secretArn = resourceIdentifiers.arn; this.encryptionKey = props.encryptionKey; - this.secretArn = resource.refAsString; } /** @@ -222,12 +235,12 @@ export enum AttachmentTargetType { /** * A database instance */ - Instance = 'AWS::RDS::DBInstance', + INSTANCE = 'AWS::RDS::DBInstance', /** * A database cluster */ - Cluster = 'AWS::RDS::DBCluster' + CLUSTER = 'AWS::RDS::DBCluster' } /** @@ -309,8 +322,8 @@ export class SecretTargetAttachment extends SecretBase implements ISecretTargetA this.encryptionKey = props.secret.encryptionKey; // This allows to reference the secret after attachment (dependency). - this.secretArn = attachment.refAsString; - this.secretTargetAttachmentSecretArn = attachment.refAsString; + this.secretArn = attachment.ref; + this.secretTargetAttachmentSecretArn = attachment.ref; } } diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index e24a685876dcd..37b9ce27b6bd2 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -90,8 +90,10 @@ "awslint": { "exclude": [ "from-signature:@aws-cdk/aws-secretsmanager.SecretTargetAttachment.fromSecretTargetAttachmentSecretArn", - "from-attributes:fromSecretTargetAttachmentAttributes" + "from-attributes:fromSecretTargetAttachmentAttributes", + "props-physical-name:@aws-cdk/aws-secretsmanager.RotationScheduleProps", + "props-physical-name:@aws-cdk/aws-secretsmanager.SecretTargetAttachmentProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts index 0dcf85527cb6e..db2ffb20e5b21 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts @@ -26,7 +26,7 @@ class SecretsManagerStack extends cdk.Stack { }); new iam.User(this, 'OtherUser', { - userName: templatedSecret.secretValueFromJson('username').toString(), + userName: cdk.PhysicalName.of(templatedSecret.secretValueFromJson('username').toString()), password: templatedSecret.secretValueFromJson('password') }); /// !hide diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index 903f51e87a716..22ab66525a380 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -315,7 +315,7 @@ export = { const target: secretsmanager.ISecretAttachmentTarget = { asSecretAttachmentTarget: () => ({ targetId: 'instance', - targetType: secretsmanager.AttachmentTargetType.Instance + targetType: secretsmanager.AttachmentTargetType.INSTANCE }) }; @@ -341,7 +341,7 @@ export = { const target: secretsmanager.ISecretAttachmentTarget = { asSecretAttachmentTarget: () => ({ targetId: 'cluster', - targetType: secretsmanager.AttachmentTargetType.Cluster + targetType: secretsmanager.AttachmentTargetType.CLUSTER }) }; const attachedSecret = secret.addTargetAttachment('AttachedSecret', { target }); diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts index 8c6135dceab3c..f3ce0c744e669 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -44,7 +44,7 @@ export class AliasTargetInstance extends InstanceBase { constructor(scope: cdk.Construct, id: string, props: AliasTargetInstanceProps) { super(scope, id); - if (props.service.namespace.type === NamespaceType.Http) { + if (props.service.namespace.type === NamespaceType.HTTP) { throw new Error('Namespace associated with Service must be a DNS Namespace.'); } @@ -56,7 +56,7 @@ export class AliasTargetInstance extends InstanceBase { throw new Error('Service must use `A` or `AAAA` records to register an AliasRecordTarget.'); } - if (props.service.routingPolicy !== RoutingPolicy.Weighted) { + if (props.service.routingPolicy !== RoutingPolicy.WEIGHTED) { throw new Error('Service must use `WEIGHTED` routing policy.'); } @@ -70,7 +70,7 @@ export class AliasTargetInstance extends InstanceBase { }); this.service = props.service; - this.instanceId = resource.refAsString; + this.instanceId = resource.ref; this.dnsName = props.dnsName; } } diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts index f75d591c79a94..3bdaf1ffeb77d 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts @@ -49,7 +49,7 @@ export class CnameInstance extends InstanceBase { constructor(scope: cdk.Construct, id: string, props: CnameInstanceProps) { super(scope, id); - if (props.service.namespace.type === NamespaceType.Http) { + if (props.service.namespace.type === NamespaceType.HTTP) { throw new Error('Namespace associated with Service must be a DNS Namespace.'); } @@ -67,7 +67,7 @@ export class CnameInstance extends InstanceBase { }); this.service = props.service; - this.instanceId = resource.refAsString; + this.instanceId = resource.ref; this.cname = props.instanceCname; } } diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts index 4dd3d4ba4a381..ac207e525a46c 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts @@ -32,7 +32,7 @@ export class HttpNamespace extends Resource implements IHttpNamespace { public namespaceName = attrs.namespaceName; public namespaceId = attrs.namespaceId; public namespaceArn = attrs.namespaceArn; - public type = NamespaceType.Http; + public type = NamespaceType.HTTP; } return new Import(scope, id); } @@ -68,7 +68,7 @@ export class HttpNamespace extends Resource implements IHttpNamespace { this.namespaceName = props.name; this.namespaceId = ns.attrId; this.namespaceArn = ns.attrArn; - this.type = NamespaceType.Http; + this.type = NamespaceType.HTTP; } /** @attribute */ diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts index 8c7b36c1d042b..1724cb7dbee3e 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts @@ -114,7 +114,7 @@ export class IpInstance extends InstanceBase { }); this.service = props.service; - this.instanceId = resource.refAsString; + this.instanceId = resource.ref; this.ipv4 = props.ipv4 || ''; this.ipv6 = props.ipv6 || ''; this.port = port; diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts index 36126d0cadda1..f690897f2fa33 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts @@ -65,17 +65,17 @@ export enum NamespaceType { /** * Choose this option if you want your application to use only API calls to discover registered instances. */ - Http = "HTTP", + HTTP = "HTTP", /** * Choose this option if you want your application to be able to discover instances using either API calls or using * DNS queries in a VPC. */ - DnsPrivate = "DNS_PRIVATE", + DNS_PRIVATE = "DNS_PRIVATE", /** * Choose this option if you want your application to be able to discover instances using either API calls or using * public DNS queries. You aren't required to use both methods. */ - DnsPublic = "DNS_PUBLIC", + DNS_PUBLIC = "DNS_PUBLIC", } diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts index de9dc52c1ff21..1346fdcf02eb7 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts @@ -38,7 +38,7 @@ export class NonIpInstance extends InstanceBase { constructor(scope: cdk.Construct, id: string, props: NonIpInstanceProps) { super(scope, id); - if (props.service.namespace.type !== NamespaceType.Http) { + if (props.service.namespace.type !== NamespaceType.HTTP) { throw new Error('This type of instance can only be registered for HTTP namespaces.'); } @@ -55,6 +55,6 @@ export class NonIpInstance extends InstanceBase { }); this.service = props.service; - this.instanceId = resource.refAsString; + this.instanceId = resource.ref; } } diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts index cfd851825d405..2b5c62639a5bc 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts @@ -2,7 +2,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import { Construct, Resource } from '@aws-cdk/cdk'; import { BaseNamespaceProps, INamespace, NamespaceType } from './namespace'; import { DnsServiceProps, Service } from './service'; -import { CfnPrivateDnsNamespace} from './servicediscovery.generated'; +import { CfnPrivateDnsNamespace } from './servicediscovery.generated'; export interface PrivateDnsNamespaceProps extends BaseNamespaceProps { /** @@ -40,7 +40,7 @@ export class PrivateDnsNamespace extends Resource implements IPrivateDnsNamespac public namespaceName = attrs.namespaceName; public namespaceId = attrs.namespaceId; public namespaceArn = attrs.namespaceArn; - public type = NamespaceType.DnsPrivate; + public type = NamespaceType.DNS_PRIVATE; } return new Import(scope, id); } @@ -80,7 +80,7 @@ export class PrivateDnsNamespace extends Resource implements IPrivateDnsNamespac this.namespaceName = props.name; this.namespaceId = ns.attrId; this.namespaceArn = ns.attrArn; - this.type = NamespaceType.DnsPrivate; + this.type = NamespaceType.DNS_PRIVATE; } /** @attribute */ diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts index 0e7a0b967ea55..012a257d3f158 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/public-dns-namespace.ts @@ -1,7 +1,7 @@ import { Construct, Resource } from '@aws-cdk/cdk'; import { BaseNamespaceProps, INamespace, NamespaceType } from './namespace'; import { DnsServiceProps, Service } from './service'; -import { CfnPublicDnsNamespace} from './servicediscovery.generated'; +import { CfnPublicDnsNamespace } from './servicediscovery.generated'; export interface PublicDnsNamespaceProps extends BaseNamespaceProps {} export interface IPublicDnsNamespace extends INamespace { } @@ -32,7 +32,7 @@ export class PublicDnsNamespace extends Resource implements IPublicDnsNamespace public namespaceName = attrs.namespaceName; public namespaceId = attrs.namespaceId; public namespaceArn = attrs.namespaceArn; - public type = NamespaceType.DnsPublic; + public type = NamespaceType.DNS_PUBLIC; } return new Import(scope, id); } @@ -68,7 +68,7 @@ export class PublicDnsNamespace extends Resource implements IPublicDnsNamespace this.namespaceName = props.name; this.namespaceId = ns.attrId; this.namespaceArn = ns.attrArn; - this.type = NamespaceType.DnsPublic; + this.type = NamespaceType.DNS_PUBLIC; } /** @attribute */ diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts index c7e81afb090d2..e17bc7bf98ede 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts @@ -1,5 +1,5 @@ import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, Resource } from '@aws-cdk/cdk'; import { AliasTargetInstance } from './alias-target-instance'; import { CnameInstance, CnameInstanceBaseProps } from './cname-instance'; import { IInstance } from './instance'; @@ -98,9 +98,9 @@ export interface DnsServiceProps extends BaseServiceProps { * The amount of time, in seconds, that you want DNS resolvers to cache the settings for this * record. * - * @default 60 + * @default Duration.minutes(1) */ - readonly dnsTtlSec?: number; + readonly dnsTtl?: Duration; /** * The routing policy that you want to apply to all DNS records that AWS Cloud Map creates when you @@ -199,7 +199,7 @@ export class Service extends ServiceBase { const namespaceType = props.namespace.type; // Validations - if (namespaceType === NamespaceType.Http && (props.routingPolicy || props.dnsRecordType)) { + if (namespaceType === NamespaceType.HTTP && (props.routingPolicy || props.dnsRecordType)) { throw new Error('Cannot specify `routingPolicy` or `dnsRecord` when using an HTTP namespace.'); } @@ -207,11 +207,11 @@ export class Service extends ServiceBase { throw new Error('Cannot specify both `healthCheckConfig` and `healthCheckCustomConfig`.'); } - if (namespaceType === NamespaceType.DnsPrivate && props.healthCheck) { + if (namespaceType === NamespaceType.DNS_PRIVATE && props.healthCheck) { throw new Error('Cannot specify `healthCheckConfig` for a Private DNS namespace.'); } - if (props.routingPolicy === RoutingPolicy.Multivalue + if (props.routingPolicy === RoutingPolicy.MULTIVALUE && props.dnsRecordType === DnsRecordType.CNAME) { throw new Error('Cannot use `CNAME` record when routing policy is `Multivalue`.'); } @@ -220,21 +220,21 @@ export class Service extends ServiceBase { // The same validation happens later on during the actual attachment // of LBs, but we need the property for the correct configuration of // routingPolicy anyway, so might as well do the validation as well. - if (props.routingPolicy === RoutingPolicy.Multivalue + if (props.routingPolicy === RoutingPolicy.MULTIVALUE && props.loadBalancer) { throw new Error('Cannot register loadbalancers when routing policy is `Multivalue`.'); } if (props.healthCheck - && props.healthCheck.type === HealthCheckType.Tcp + && props.healthCheck.type === HealthCheckType.TCP && props.healthCheck.resourcePath) { throw new Error('Cannot specify `resourcePath` when using a `TCP` health check.'); } // Set defaults where necessary const routingPolicy = (props.dnsRecordType === DnsRecordType.CNAME) || props.loadBalancer - ? RoutingPolicy.Weighted - : RoutingPolicy.Multivalue; + ? RoutingPolicy.WEIGHTED + : RoutingPolicy.MULTIVALUE; const dnsRecordType = props.dnsRecordType || DnsRecordType.A; @@ -245,18 +245,18 @@ export class Service extends ServiceBase { throw new Error('Must support `A` or `AAAA` records to register loadbalancers.'); } - const dnsConfig: CfnService.DnsConfigProperty | undefined = props.namespace.type === NamespaceType.Http + const dnsConfig: CfnService.DnsConfigProperty | undefined = props.namespace.type === NamespaceType.HTTP ? undefined : { - dnsRecords: renderDnsRecords(dnsRecordType, props.dnsTtlSec), + dnsRecords: renderDnsRecords(dnsRecordType, props.dnsTtl), namespaceId: props.namespace.namespaceId, routingPolicy, }; const healthCheckConfigDefaults = { - type: HealthCheckType.Http, + type: HealthCheckType.HTTP, failureThreshold: 1, - resourcePath: props.healthCheck && props.healthCheck.type !== HealthCheckType.Tcp + resourcePath: props.healthCheck && props.healthCheck.type !== HealthCheckType.TCP ? '/' : undefined }; @@ -324,8 +324,8 @@ export class Service extends ServiceBase { } } -function renderDnsRecords(dnsRecordType: DnsRecordType, dnsTtlSec?: number): CfnService.DnsRecordProperty[] { - const ttl = dnsTtlSec !== undefined ? dnsTtlSec : 60; +function renderDnsRecords(dnsRecordType: DnsRecordType, dnsTtl: Duration = Duration.minutes(1)): CfnService.DnsRecordProperty[] { + const ttl = dnsTtl.toSeconds(); if (dnsRecordType === DnsRecordType.A_AAAA) { return [{ @@ -414,13 +414,13 @@ export enum RoutingPolicy { * Route 53 returns the applicable value from one randomly selected instance from among the instances that you * registered using the same service. */ - Weighted = "WEIGHTED", + WEIGHTED = "WEIGHTED", /** * If you define a health check for the service and the health check is healthy, Route 53 returns the applicable value * for up to eight instances. */ - Multivalue = "MULTIVALUE", + MULTIVALUE = "MULTIVALUE", } export enum HealthCheckType { @@ -428,18 +428,18 @@ export enum HealthCheckType { * Route 53 tries to establish a TCP connection. If successful, Route 53 submits an HTTP request and waits for an HTTP * status code of 200 or greater and less than 400. */ - Http = "HTTP", + HTTP = "HTTP", /** * Route 53 tries to establish a TCP connection. If successful, Route 53 submits an HTTPS request and waits for an * HTTP status code of 200 or greater and less than 400. If you specify HTTPS for the value of Type, the endpoint * must support TLS v1.0 or later. */ - Https = "HTTPS", + HTTPS = "HTTPS", /** * Route 53 tries to establish a TCP connection. * If you specify TCP for Type, don't specify a value for ResourcePath. */ - Tcp = "TCP", + TCP = "TCP", } diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index df1077231b0bb..d476e87d3742f 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -85,5 +85,17 @@ "engines": { "node": ">= 8.10.0" }, + "awslint": { + "exclude": [ + "props-physical-name:@aws-cdk/aws-servicediscovery.HttpNamespaceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.PublicDnsNamespaceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.PrivateDnsNamespaceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.AliasTargetInstanceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.CnameInstanceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.IpInstanceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.NonIpInstanceProps", + "props-physical-name:@aws-cdk/aws-servicediscovery.ServiceProps" + ] + }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts index 58b0adba8b412..10d8f50be29cc 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts @@ -11,7 +11,7 @@ const namespace = new servicediscovery.PublicDnsNamespace(stack, 'Namespace', { const service = namespace.createService('Service', { name: 'foo', dnsRecordType: servicediscovery.DnsRecordType.CNAME, - dnsTtlSec: 30 + dnsTtl: cdk.Duration.seconds(30) }); service.registerCnameInstance('CnameInstance', { diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts index 372a4cc8b8560..3b54636ae29bc 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts @@ -19,7 +19,7 @@ service1.registerNonIpInstance('NonIpInstance', { const service2 = namespace.createService('IpService', { description: 'service registering ip instances', healthCheck: { - type: servicediscovery.HealthCheckType.Http, + type: servicediscovery.HealthCheckType.HTTP, resourcePath: '/check' } }); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.ts index d785f0d960713..3aeecdc78abaf 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.ts @@ -15,7 +15,7 @@ const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { const service = namespace.createService('Service', { dnsRecordType: servicediscovery.DnsRecordType.A_AAAA, - dnsTtlSec: 30, + dnsTtl: cdk.Duration.seconds(30), loadBalancer: true }); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts index 43a997827b6d4..964054033b7d7 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts @@ -11,9 +11,9 @@ const namespace = new servicediscovery.PublicDnsNamespace(stack, 'Namespace', { const service = namespace.createService('Service', { name: 'foo', dnsRecordType: servicediscovery.DnsRecordType.A, - dnsTtlSec: 30, + dnsTtl: cdk.Duration.seconds(30), healthCheck: { - type: servicediscovery.HealthCheckType.Https, + type: servicediscovery.HealthCheckType.HTTPS, resourcePath: '/healthcheck', failureThreshold: 2 } diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts index fbaacf41587d4..7432cf7edab2b 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts @@ -312,7 +312,7 @@ export = { }); const service = namespace.createService('MyService', { - routingPolicy: servicediscovery.RoutingPolicy.Multivalue + routingPolicy: servicediscovery.RoutingPolicy.MULTIVALUE }); const vpc = new ec2.Vpc(stack, 'MyVPC'); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts index d371d2a3ec47f..8f1c5c717109d 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts @@ -64,7 +64,7 @@ export = { name: 'service', description: 'service description', healthCheck: { - type: servicediscovery.HealthCheckType.Http, + type: servicediscovery.HealthCheckType.HTTP, resourcePath: '/check' } }); @@ -335,7 +335,7 @@ export = { namespace.createService('MyService', { name: 'service', dnsRecordType: servicediscovery.DnsRecordType.CNAME, - routingPolicy: servicediscovery.RoutingPolicy.Multivalue, + routingPolicy: servicediscovery.RoutingPolicy.MULTIVALUE, }); }, /Cannot use `CNAME` record when routing policy is `Multivalue`./); @@ -355,7 +355,7 @@ export = { namespace.createService('MyService', { name: 'service', healthCheck: { - type: servicediscovery.HealthCheckType.Tcp, + type: servicediscovery.HealthCheckType.TCP, resourcePath: '/check' } }); @@ -395,7 +395,7 @@ export = { test.throws(() => { namespace.createService('MyService', { loadBalancer: true, - routingPolicy: servicediscovery.RoutingPolicy.Multivalue + routingPolicy: servicediscovery.RoutingPolicy.MULTIVALUE }); }, /Cannot register loadbalancers when routing policy is `Multivalue`./); diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts b/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts index b9ff54e33d22c..14058110b6fd6 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts @@ -1,4 +1,4 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, PhysicalName, Resource } from '@aws-cdk/cdk'; import { CfnReceiptFilter } from './ses.generated'; /** @@ -8,12 +8,12 @@ export enum ReceiptFilterPolicy { /** * Allow the ip address or range. */ - Allow = 'Allow', + ALLOW = 'Allow', /** * Block the ip address or range. */ - Block = 'Block' + BLOCK = 'Block' } /** @@ -25,7 +25,7 @@ export interface ReceiptFilterProps { * * @default a CloudFormation generated name */ - readonly name?: string; + readonly receiptFilterName?: PhysicalName; /** * The ip address or range to filter. @@ -47,16 +47,18 @@ export interface ReceiptFilterProps { * block all receipt filter. */ export class ReceiptFilter extends Resource { - constructor(scope: Construct, id: string, props?: ReceiptFilterProps) { - super(scope, id); + constructor(scope: Construct, id: string, props: ReceiptFilterProps = {}) { + super(scope, id, { + physicalName: props.receiptFilterName, + }); new CfnReceiptFilter(this, 'Resource', { filter: { ipFilter: { - cidr: (props && props.ip) || '0.0.0.0/0', - policy: (props && props.policy) || ReceiptFilterPolicy.Block + cidr: props.ip || '0.0.0.0/0', + policy: props.policy || ReceiptFilterPolicy.BLOCK, }, - name: props ? props.name : undefined + name: this.physicalName, } }); } @@ -84,7 +86,7 @@ export class WhiteListReceiptFilter extends Construct { props.ips.forEach(ip => { new ReceiptFilter(this, `Allow${ip.replace(/[^\d]/g, '')}`, { ip, - policy: ReceiptFilterPolicy.Allow + policy: ReceiptFilterPolicy.ALLOW }); }); } diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts index d004a729a51a4..62e6e7c39f611 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts @@ -230,14 +230,14 @@ export enum LambdaInvocationType { /** * The function will be invoked asynchronously. */ - Event = 'Event', + EVENT = 'Event', /** * The function will be invoked sychronously. Use RequestResponse only when * you want to make a mail flow decision, such as whether to stop the receipt * rule or the receipt rule set. */ - RequestResponse = 'RequestResponse', + REQUEST_RESPONSE = 'RequestResponse', } /** @@ -391,7 +391,7 @@ export enum EmailEncoding { /** * Base 64 */ - Base64 = 'Base64', + BASE64 = 'Base64', /** * UTF-8 diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts index e48a4705bb13a..e753a87a47c90 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts @@ -1,4 +1,4 @@ -import { Construct, IResource, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, PhysicalName, Resource } from '@aws-cdk/cdk'; import { DropSpamReceiptRule, ReceiptRule, ReceiptRuleOptions } from './receipt-rule'; import { CfnReceiptRuleSet } from './ses.generated'; @@ -28,7 +28,7 @@ export interface ReceiptRuleSetProps { * * @default - A CloudFormation generated name. */ - readonly name?: string; + readonly receiptRuleSetName?: PhysicalName; /** * The list of rules to add to this rule set. Rules are added in the same @@ -96,14 +96,16 @@ export class ReceiptRuleSet extends ReceiptRuleSetBase { public readonly receiptRuleSetName: string; - constructor(scope: Construct, id: string, props?: ReceiptRuleSetProps) { - super(scope, id); + constructor(scope: Construct, id: string, props: ReceiptRuleSetProps = {}) { + super(scope, id, { + physicalName: props.receiptRuleSetName, + }); const resource = new CfnReceiptRuleSet(this, 'Resource', { - ruleSetName: props ? props.name : undefined + ruleSetName: this.physicalName, }); - this.receiptRuleSetName = resource.refAsString; + this.receiptRuleSetName = resource.ref; if (props) { const rules = props.rules || []; diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts index 5cfc91e7a60d6..30be18d86c53c 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts @@ -1,5 +1,5 @@ import lambda = require('@aws-cdk/aws-lambda'); -import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource } from '@aws-cdk/cdk'; import { IReceiptRuleAction, LambdaInvocationType, ReceiptRuleActionProps, ReceiptRuleLambdaAction } from './receipt-rule-action'; import { IReceiptRuleSet } from './receipt-rule-set'; import { CfnReceiptRule } from './ses.generated'; @@ -22,12 +22,12 @@ export enum TlsPolicy { /** * Do not check for TLS. */ - Optional = 'Optional', + OPTIONAL = 'Optional', /** * Bounce emails that are not received over TLS. */ - Require = 'Require' + REQUIRE = 'Require' } /** @@ -62,7 +62,7 @@ export interface ReceiptRuleOptions { * * @default - A CloudFormation generated name. */ - readonly name?: string; + readonly receiptRuleName?: PhysicalName; /** * The recipient domains and email addresses that the receipt rule applies to. @@ -113,14 +113,16 @@ export class ReceiptRule extends Resource implements IReceiptRule { private readonly renderedActions = new Array(); constructor(scope: Construct, id: string, props: ReceiptRuleProps) { - super(scope, id); + super(scope, id, { + physicalName: props.receiptRuleName, + }); const resource = new CfnReceiptRule(this, 'Resource', { after: props.after ? props.after.receiptRuleName : undefined, rule: { actions: Lazy.anyValue({ produce: () => this.getRenderedActions() }), enabled: props.enabled === undefined ? true : props.enabled, - name: props.name, + name: this.physicalName, recipients: props.recipients, scanEnabled: props.scanEnabled, tlsPolicy: props.tlsPolicy @@ -128,7 +130,7 @@ export class ReceiptRule extends Resource implements IReceiptRule { ruleSetName: props.ruleSet.receiptRuleSetName }); - this.receiptRuleName = resource.refAsString; + this.receiptRuleName = resource.ref; if (props.actions) { props.actions.forEach(action => this.addAction(action)); @@ -180,7 +182,7 @@ export class DropSpamReceiptRule extends Construct { actions: [ new ReceiptRuleLambdaAction({ function: fn, - invocationType: LambdaInvocationType.RequestResponse + invocationType: LambdaInvocationType.REQUEST_RESPONSE }) ], scanEnabled: true, diff --git a/packages/@aws-cdk/aws-ses/test/integ.receipt.ts b/packages/@aws-cdk/aws-ses/test/integ.receipt.ts index ea05d0a221cf0..cc40aa0653b01 100644 --- a/packages/@aws-cdk/aws-ses/test/integ.receipt.ts +++ b/packages/@aws-cdk/aws-ses/test/integ.receipt.ts @@ -33,7 +33,7 @@ const firstRule = ruleSet.addRule('FirstRule', { }), new ses.ReceiptRuleLambdaAction({ function: fn, - invocationType: ses.LambdaInvocationType.RequestResponse, + invocationType: ses.LambdaInvocationType.REQUEST_RESPONSE, topic }), new ses.ReceiptRuleS3Action({ @@ -43,14 +43,14 @@ const firstRule = ruleSet.addRule('FirstRule', { topic }), new ses.ReceiptRuleSnsAction({ - encoding: ses.EmailEncoding.Base64, + encoding: ses.EmailEncoding.BASE64, topic }) ], - name: 'FirstRule', + receiptRuleName: cdk.PhysicalName.of('FirstRule'), recipients: ['cdk-ses-receipt-test@yopmail.com'], scanEnabled: true, - tlsPolicy: ses.TlsPolicy.Require, + tlsPolicy: ses.TlsPolicy.REQUIRE, }); firstRule.addAction( diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts index b0d40052a3534..64809ec58ad78 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-filter.ts @@ -1,5 +1,5 @@ import { expect } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ReceiptFilter, ReceiptFilterPolicy, WhiteListReceiptFilter } from '../lib'; @@ -13,8 +13,8 @@ export = { // WHEN new ReceiptFilter(stack, 'Filter', { ip: '1.2.3.4/16', - name: 'MyFilter', - policy: ReceiptFilterPolicy.Block + receiptFilterName: PhysicalName.of('MyFilter'), + policy: ReceiptFilterPolicy.BLOCK }); // THEN diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts index 258a1872b77e9..ea60d6c59a187 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts @@ -145,7 +145,7 @@ export = { actions: [ new ReceiptRuleLambdaAction({ function: fn, - invocationType: LambdaInvocationType.RequestResponse, + invocationType: LambdaInvocationType.REQUEST_RESPONSE, topic }) ] @@ -385,7 +385,7 @@ export = { { actions: [ new ReceiptRuleSnsAction({ - encoding: EmailEncoding.Base64, + encoding: EmailEncoding.BASE64, topic }) ] diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts index 56707a519b810..55de235c8f73b 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-set.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ReceiptRuleSet } from '../lib'; @@ -12,7 +12,7 @@ export = { // WHEN new ReceiptRuleSet(stack, 'RuleSet', { - name: 'MyRuleSet' + receiptRuleSetName: PhysicalName.of('MyRuleSet'), }); // THEN diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts index 38854b1a2d725..6ecae84d2b671 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule.ts @@ -1,5 +1,5 @@ import { expect } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ReceiptRule, ReceiptRuleSet, TlsPolicy } from '../lib'; @@ -14,14 +14,14 @@ export = { new ReceiptRuleSet(stack, 'RuleSet', { rules: [ { - name: 'FirstRule', + receiptRuleName: PhysicalName.of('FirstRule'), }, { enabled: false, - name: 'SecondRule', + receiptRuleName: PhysicalName.of('SecondRule'), recipients: ['hello@aws.com'], scanEnabled: true, - tlsPolicy: TlsPolicy.Require + tlsPolicy: TlsPolicy.REQUIRE } ] }); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts index b678b9e102cfd..97a4c9846774b 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/email.ts @@ -1,10 +1,10 @@ import sns = require('@aws-cdk/aws-sns'); -import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Options for email subscriptions. */ -export interface EmailSubscriptionProps { +export interface EmailSubscriptionProps extends SubscriptionProps { /** * Indicates if the full notification JSON should be sent to the email * address or just the message text. @@ -23,11 +23,12 @@ export class EmailSubscription implements sns.ITopicSubscription { constructor(private readonly emailAddress: string, private readonly props: EmailSubscriptionProps = {}) { } - public bind(scope: Construct, topic: sns.ITopic): void { - new sns.Subscription(scope, this.emailAddress, { - topic, + public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { + return { + subscriberId: this.emailAddress, endpoint: this.emailAddress, - protocol: this.props.json ? sns.SubscriptionProtocol.EmailJson : sns.SubscriptionProtocol.Email - }); + protocol: this.props.json ? sns.SubscriptionProtocol.EMAIL_JSON : sns.SubscriptionProtocol.EMAIL, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts index c63c355ff3aa1..a6646dfa2e0af 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/index.ts @@ -1,3 +1,4 @@ +export * from './subscription'; export * from './email'; export * from './lambda'; export * from './sqs'; diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index 114439087a581..d5e2c34a52770 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -2,37 +2,38 @@ import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import sns = require('@aws-cdk/aws-sns'); import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; +/** + * Properties for a Lambda subscription + */ +export interface LambdaSubscriptionProps extends SubscriptionProps { + +} /** * Use a Lambda function as a subscription target */ export class LambdaSubscription implements sns.ITopicSubscription { - constructor(private readonly fn: lambda.IFunction) { + constructor(private readonly fn: lambda.IFunction, private readonly props: LambdaSubscriptionProps = {}) { } - public bind(_scope: Construct, topic: sns.ITopic): void { + public bind(topic: sns.ITopic): sns.TopicSubscriptionConfig { // Create subscription under *consuming* construct to make sure it ends up // in the correct stack in cases of cross-stack subscriptions. if (!Construct.isConstruct(this.fn)) { throw new Error(`The supplied lambda Function object must be an instance of Construct`); } - // we use the target name as the subscription's. there's no meaning to - // subscribing the same queue twice on the same topic. - const subscriptionName = topic.node.id + 'Subscription'; - if (this.fn.node.tryFindChild(subscriptionName)) { - throw new Error(`A subscription between the topic ${topic.node.id} and the lambda ${this.fn.node.id} already exists`); - } - - new sns.Subscription(this.fn, subscriptionName, { - topic, - endpoint: this.fn.functionArn, - protocol: sns.SubscriptionProtocol.Lambda, - }); - this.fn.addPermission(topic.node.id, { sourceArn: topic.topicArn, principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); + + return { + subscriberId: this.fn.node.id, + endpoint: this.fn.functionArn, + protocol: sns.SubscriptionProtocol.LAMBDA, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 5a3e92569a171..02092f1dd9ac6 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -2,11 +2,12 @@ import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); import sqs = require('@aws-cdk/aws-sqs'); import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Properties for an SQS subscription */ -export interface SqsSubscriptionProps { +export interface SqsSubscriptionProps extends SubscriptionProps { /** * The message to the queue is the same as it was sent to the topic * @@ -24,27 +25,13 @@ export class SqsSubscription implements sns.ITopicSubscription { constructor(private readonly queue: sqs.IQueue, private readonly props: SqsSubscriptionProps = {}) { } - public bind(_scope: Construct, topic: sns.ITopic): void { + public bind(topic: sns.ITopic): sns.TopicSubscriptionConfig { // Create subscription under *consuming* construct to make sure it ends up // in the correct stack in cases of cross-stack subscriptions. if (!Construct.isConstruct(this.queue)) { throw new Error(`The supplied Queue object must be an instance of Construct`); } - // we use the queue name as the subscription's. there's no meaning to - // subscribing the same queue twice on the same topic. - const subscriptionName = topic.node.id + 'Subscription'; - if (this.queue.node.tryFindChild(subscriptionName)) { - throw new Error(`A subscription between the topic ${topic.node.id} and the queue ${this.queue.node.id} already exists`); - } - - new sns.Subscription(this.queue, subscriptionName, { - topic, - endpoint: this.queue.queueArn, - protocol: sns.SubscriptionProtocol.Sqs, - rawMessageDelivery: this.props.rawMessageDelivery, - }); - // add a statement to the queue resource policy which allows this topic // to send messages to the queue. this.queue.addToResourcePolicy(new iam.PolicyStatement({ @@ -55,5 +42,13 @@ export class SqsSubscription implements sns.ITopicSubscription { ArnEquals: { 'aws:SourceArn': topic.topicArn } } })); + + return { + subscriberId: this.queue.node.id, + endpoint: this.queue.queueArn, + protocol: sns.SubscriptionProtocol.SQS, + rawMessageDelivery: this.props.rawMessageDelivery, + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts new file mode 100644 index 0000000000000..603f54c835583 --- /dev/null +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/subscription.ts @@ -0,0 +1,10 @@ +import sns = require('@aws-cdk/aws-sns'); + +export interface SubscriptionProps { + /** + * The filter policy. + * + * @default - all messages are delivered + */ + readonly filterPolicy?: { [attribute: string]: sns.SubscriptionFilter }; +} diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts index 7bcdf82b38e08..11712dafdfdd5 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/url.ts @@ -1,10 +1,11 @@ import sns = require('@aws-cdk/aws-sns'); import { Construct, Token } from '@aws-cdk/cdk'; +import { SubscriptionProps } from './subscription'; /** * Options for URL subscriptions. */ -export interface UrlSubscriptionProps { +export interface UrlSubscriptionProps extends SubscriptionProps { /** * The message to the queue is the same as it was sent to the topic * @@ -29,12 +30,13 @@ export class UrlSubscription implements sns.ITopicSubscription { } } - public bind(scope: Construct, topic: sns.ITopic): void { - new sns.Subscription(scope, Token.isUnresolved(this.url) ? 'UnresolvedUrl' : this.url, { - topic, + public bind(_topic: sns.ITopic): sns.TopicSubscriptionConfig { + return { + subscriberId: this.url, endpoint: this.url, protocol: this.protocol, rawMessageDelivery: this.props.rawMessageDelivery, - }); + filterPolicy: this.props.filterPolicy, + }; } } diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json index 62614184e66a6..c2b5ff331efd6 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.expected.json @@ -3,6 +3,65 @@ "MyTopic86869434": { "Type": "AWS::SNS::Topic" }, + "MyTopicEchoD1E0EE5C": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "MyTopic86869434" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] + } + } + }, + "MyTopicFiltered55457D11": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "MyTopic86869434" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Filtered186C0D0A", + "Arn" + ] + }, + "FilterPolicy": { + "color": [ + "red", + { + "prefix": "bl" + }, + { + "prefix": "ye" + } + ], + "size": [ + { + "anything-but": [ + "small", + "medium" + ] + } + ], + "price": [ + { + "numeric": [ + ">=", + 100, + "<=", + 200 + ] + } + ] + } + } + }, "EchoServiceRoleBE28060B": { "Type": "AWS::IAM::Role", "Properties": { @@ -63,28 +122,89 @@ "EchoServiceRoleBE28060B" ] }, - "EchoMyTopicSubscriptionA634C6D7": { - "Type": "AWS::SNS::Subscription", + "EchoMyTopicF6EBB45F": { + "Type": "AWS::Lambda::Permission", "Properties": { - "Protocol": "lambda", - "TopicArn": { - "Ref": "MyTopic86869434" - }, - "Endpoint": { + "Action": "lambda:InvokeFunction", + "FunctionName": { "Fn::GetAtt": [ "Echo11F3FB29", "Arn" ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "MyTopic86869434" } } }, - "EchoMyTopicF6EBB45F": { + "FilteredServiceRole16D9DDC1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "Filtered186C0D0A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = function handler(event, _context, callback) {\n // tslint:disable:no-console\n console.log('====================================================');\n console.log(JSON.stringify(event, undefined, 2));\n console.log('====================================================');\n return callback(undefined, event);\n}" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FilteredServiceRole16D9DDC1", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "FilteredServiceRole16D9DDC1" + ] + }, + "FilteredMyTopic804BCBC3": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Fn::GetAtt": [ - "Echo11F3FB29", + "Filtered186C0D0A", "Arn" ] }, @@ -95,4 +215,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts index 4a6efc3f5699d..bd4e992df9c24 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-lambda.ts @@ -3,7 +3,7 @@ import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); import subs = require('../lib'); -class SnsToSqs extends cdk.Stack { +class SnsToLambda extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); @@ -16,12 +16,33 @@ class SnsToSqs extends cdk.Stack { }); topic.addSubscription(new subs.LambdaSubscription(fction)); + + const fctionFiltered = new lambda.Function(this, 'Filtered', { + handler: 'index.handler', + runtime: lambda.Runtime.Nodejs810, + code: lambda.Code.inline(`exports.handler = ${handler.toString()}`) + }); + + topic.addSubscription(new subs.LambdaSubscription(fctionFiltered, { + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red'], + matchPrefixes: ['bl', 'ye'], + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 } + }) + } + })); } } const app = new cdk.App(); -new SnsToSqs(app, 'aws-cdk-sns-lambda'); +new SnsToLambda(app, 'aws-cdk-sns-lambda'); app.synth(); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json index 868a13aed58da..31a0f99f6514a 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/integ.sns-sqs.lit.expected.json @@ -3,10 +3,7 @@ "MyTopic86869434": { "Type": "AWS::SNS::Topic" }, - "MyQueueE6CA6235": { - "Type": "AWS::SQS::Queue" - }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "sqs", @@ -21,6 +18,9 @@ } } }, + "MyQueueE6CA6235": { + "Type": "AWS::SQS::Queue" + }, "MyQueuePolicy6BBEDDAC": { "Type": "AWS::SQS::QueuePolicy", "Properties": { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts index 70dbacc1cacc1..8e2f152b49f38 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/test/subs.test.ts @@ -2,7 +2,7 @@ import '@aws-cdk/assert/jest'; import lambda = require('@aws-cdk/aws-lambda'); import sns = require('@aws-cdk/aws-sns'); import sqs = require('@aws-cdk/aws-sqs'); -import { Stack } from '@aws-cdk/cdk'; +import { PhysicalName, Stack } from '@aws-cdk/cdk'; import subs = require('../lib'); // tslint:disable:object-literal-key-quotes @@ -13,7 +13,7 @@ let topic: sns.Topic; beforeEach(() => { stack = new Stack(); topic = new sns.Topic(stack, 'MyTopic', { - topicName: 'topicName', + topicName: PhysicalName.of('topicName'), displayName: 'displayName' }); }); @@ -85,7 +85,7 @@ test('queue subscription', () => { "TopicName": "topicName" } }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -180,7 +180,7 @@ test('lambda subscription', () => { "TopicName": "topicName" } }, - "MyFuncMyTopicSubscription708A6535": { + "MyTopicMyFunc853BC1D3": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -300,7 +300,7 @@ test('multiple subscriptions', () => { "TopicName": "topicName" } }, - "MyQueueMyTopicSubscriptionEB66AD1B": { + "MyTopicMyQueueFA241964": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -315,7 +315,7 @@ test('multiple subscriptions', () => { } } }, - "MyFuncMyTopicSubscription708A6535": { + "MyTopicMyFunc853BC1D3": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": { @@ -437,3 +437,67 @@ test('multiple subscriptions', () => { } }); }); + +test('throws with mutliple subscriptions of the same subscriber', () => { + const queue = new sqs.Queue(stack, 'MyQueue'); + + topic.addSubscription(new subs.SqsSubscription(queue)); + + expect(() => topic.addSubscription(new subs.SqsSubscription(queue))) + .toThrowError(/subscriber MyQueue already exists/); +}); + +test('with filter policy', () => { + const fction = new lambda.Function(stack, 'MyFunc', { + runtime: lambda.Runtime.Nodejs810, + handler: 'index.handler', + code: lambda.Code.inline('exports.handler = function(e, c, cb) { return cb() }') + }); + + topic.addSubscription(new subs.LambdaSubscription(fction, { + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red'], + matchPrefixes: ['bl', 'ye'], + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 } + }) + } + })); + + expect(stack).toHaveResource('AWS::SNS::Subscription', { + "FilterPolicy": { + "color": [ + "red", + { + "prefix": "bl" + }, + { + "prefix": "ye" + } + ], + "size": [ + { + "anything-but": [ + "small", + "medium" + ] + } + ], + "price": [ + { + "numeric": [ + ">=", + 100, + "<=", + 200 + ] + } + ] + } + }); +}); diff --git a/packages/@aws-cdk/aws-sns/README.md b/packages/@aws-cdk/aws-sns/README.md index 15e8f180e2609..82a1cf6a8d022 100644 --- a/packages/@aws-cdk/aws-sns/README.md +++ b/packages/@aws-cdk/aws-sns/README.md @@ -51,6 +51,35 @@ myTopic.addSubscription(new subs.SqsSubscription(queue)); Note that subscriptions of queues in different accounts need to be manually confirmed by reading the initial message from the queue and visiting the link found in it. +#### Filter policy +A filter policy can be specified when subscribing an endpoint to a topic. + +Example with a Lambda subscription: +```ts +const myTopic = new sns.Topic(this, 'MyTopic'); +const fn = new lambda.Function(this, 'Function', ...); + +// Lambda should receive only message matching the following conditions on attributes: +// color: 'red' or 'orange' or begins with 'bl' +// size: anything but 'small' or 'medium' +// price: between 100 and 200 or greater than 300 +topic.subscribeLambda(new subs.LambdaSubscription(fn, { + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red', 'orange'], + matchPrefixes: ['bl'] + }), + size: sns.SubscriptionFilter.stringFilter({ + blacklist: ['small', 'medium'], + }), + price: sns.SubscriptionFilter.numericFilter({ + between: { start: 100, stop: 200 }, + greaterThan: 300 + }) + } +})); +``` + ### CloudWatch Event Rule Target SNS topics can be used as targets for CloudWatch event rules. diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index cc33331121840..cdcb67084921e 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -3,6 +3,7 @@ export * from './topic'; export * from './topic-base'; export * from './subscription'; export * from './subscriber'; +export * from './subscription-filter'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; diff --git a/packages/@aws-cdk/aws-sns/lib/subscriber.ts b/packages/@aws-cdk/aws-sns/lib/subscriber.ts index 82cd69d1bbc6d..1077732ef8df9 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscriber.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscriber.ts @@ -1,9 +1,20 @@ -import { Construct } from '@aws-cdk/cdk'; +import { SubscriptionOptions } from './subscription'; import { ITopic } from './topic-base'; +/** + * Subscription configuration + */ +export interface TopicSubscriptionConfig extends SubscriptionOptions { + /** + * The id of the subscriber. Will be used as the id for the subscription in + * the topic's scope. + */ + readonly subscriberId: string; +} + /** * Topic subscription */ export interface ITopicSubscription { - bind(scope: Construct, topic: ITopic): void; + bind(topic: ITopic): TopicSubscriptionConfig; } diff --git a/packages/@aws-cdk/aws-sns/lib/subscription-filter.ts b/packages/@aws-cdk/aws-sns/lib/subscription-filter.ts new file mode 100644 index 0000000000000..188e843cd1fc7 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/lib/subscription-filter.ts @@ -0,0 +1,139 @@ +/** + * Conditions that can be applied to string attributes. + */ +export interface StringConditions { + /** + * Match one or more values. + */ + readonly whitelist?: string[]; + + /** + * Match any value that doesn't include any of the specified values. + */ + readonly blacklist?: string[]; + + /** + * Matches values that begins with the specified prefixes. + */ + readonly matchPrefixes?: string[]; +} + +/** + * Between condition for a numeric attribute. + */ +export interface BetweenCondition { + /** + * The start value. + */ + readonly start: number; + + /** + * The stop value. + */ + readonly stop: number; +} + +/** + * Conditions that can be applied to numeric attributes. + */ +export interface NumericConditions { + /** + * Match one or more values. + */ + readonly whitelist?: number[]; + + /** + * Match values that are greater than the specified value. + */ + readonly greaterThan?: number; + + /** + * Match values that are greater than or equal to the specified value. + */ + readonly greaterThanOrEqualTo?: number; + + /** + * Match values that are less than the specified value. + */ + readonly lessThan?: number; + + /** + * Match values that are less than or equal to the specified value. + */ + readonly lessThanOrEqualTo?: number; + + /** + * Match values that are between the specified values. + */ + readonly between?: BetweenCondition; + + /** + * Match values that are strictly between the specified values. + */ + readonly betweenStrict?: BetweenCondition; +} + +/** + * A subscription filter for an attribute. + */ +export class SubscriptionFilter { + /** + * Returns a subscription filter for a string attribute. + */ + public static stringFilter(stringConditions: StringConditions) { + const conditions = []; + + if (stringConditions.whitelist) { + conditions.push(...stringConditions.whitelist); + } + + if (stringConditions.blacklist) { + conditions.push({ 'anything-but': stringConditions.blacklist }); + } + + if (stringConditions.matchPrefixes) { + conditions.push(...stringConditions.matchPrefixes.map(p => ({ prefix: p }))); + } + + return new SubscriptionFilter(conditions); + } + + /** + * Returns a subscription filter for a numeric attribute. + */ + public static numericFilter(numericConditions: NumericConditions) { + const conditions = []; + + if (numericConditions.whitelist) { + conditions.push(...numericConditions.whitelist.map(v => ({ numeric: ['=', v] }))); + } + + if (numericConditions.greaterThan) { + conditions.push({ numeric: ['>', numericConditions.greaterThan] }); + } + + if (numericConditions.greaterThanOrEqualTo) { + conditions.push({ numeric: ['>=', numericConditions.greaterThanOrEqualTo] }); + } + + if (numericConditions.lessThan) { + conditions.push({ numeric: ['<', numericConditions.lessThan] }); + } + + if (numericConditions.lessThanOrEqualTo) { + conditions.push({ numeric: ['<=', numericConditions.lessThanOrEqualTo] }); + } + + if (numericConditions.between) { + conditions.push({ numeric: ['>=', numericConditions.between.start, '<=', numericConditions.between.stop ]}); + } + + if (numericConditions.betweenStrict) { + conditions.push({ numeric: ['>', numericConditions.betweenStrict.start, '<', numericConditions.betweenStrict.stop ]}); + } + + return new SubscriptionFilter(conditions); + } + + constructor(public readonly conditions: any[] = []) {} +} diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index 184227aac4b90..9c8a5c4bcaf2a 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -1,11 +1,12 @@ import { Construct, Resource } from '@aws-cdk/cdk'; import { CfnSubscription } from './sns.generated'; +import { SubscriptionFilter } from './subscription-filter'; import { ITopic } from './topic-base'; /** - * Properties for creating a new subscription + * Options for creating a new subscription */ -export interface SubscriptionProps { +export interface SubscriptionOptions { /** * What type of subscription to add. */ @@ -18,11 +19,6 @@ export interface SubscriptionProps { */ readonly endpoint: string; - /** - * The topic to subscribe to. - */ - readonly topic: ITopic; - /** * true if raw message delivery is enabled for the subscription. Raw messages are free of JSON formatting and can be * sent to HTTP/S and Amazon SQS endpoints. For more information, see GetSubscriptionAttributes in the Amazon Simple @@ -31,15 +27,33 @@ export interface SubscriptionProps { * @default false */ readonly rawMessageDelivery?: boolean; + + /** + * The filter policy. + * + * @default - all messages are delivered + */ + readonly filterPolicy?: { [attribute: string]: SubscriptionFilter }; +} +/** + * Properties for creating a new subscription + */ +export interface SubscriptionProps extends SubscriptionOptions { + /** + * The topic to subscribe to. + */ + readonly topic: ITopic; } /** * A new subscription. * - * Prefer to use the `ITopic.subscribeXxx()` methods to creating instances of + * Prefer to use the `ITopic.addSubscription()` methods to create instances of * this class. */ export class Subscription extends Resource { + private readonly filterPolicy?: { [attribute: string]: any[] }; + constructor(scope: Construct, id: string, props: SubscriptionProps) { super(scope, id); @@ -47,11 +61,30 @@ export class Subscription extends Resource { throw new Error('Raw message delivery can only be enabled for HTTP/S and SQS subscriptions.'); } + if (props.filterPolicy) { + if (Object.keys(props.filterPolicy).length > 5) { + throw new Error('A filter policy can have a maximum of 5 attribute names.'); + } + + this.filterPolicy = Object.entries(props.filterPolicy) + .reduce( + (acc, [k, v]) => ({ ...acc, [k]: v.conditions }), + {} + ); + + let total = 1; + Object.values(this.filterPolicy).forEach(filter => { total *= filter.length; }); + if (total > 100) { + throw new Error(`The total combination of values (${total}) must not exceed 100.`); + } + } + new CfnSubscription(this, 'Resource', { endpoint: props.endpoint, protocol: props.protocol, topicArn: props.topic.topicArn, rawMessageDelivery: props.rawMessageDelivery, + filterPolicy: this.filterPolicy, }); } @@ -64,40 +97,40 @@ export enum SubscriptionProtocol { /** * JSON-encoded message is POSTED to an HTTP url. */ - Http = 'http', + HTTP = 'http', /** * JSON-encoded message is POSTed to an HTTPS url. */ - Https = 'https', + HTTPS = 'https', /** * Notifications are sent via email. */ - Email = 'email', + EMAIL = 'email', /** * Notifications are JSON-encoded and sent via mail. */ - EmailJson = 'email-json', + EMAIL_JSON = 'email-json', /** * Notification is delivered by SMS */ - Sms = 'sms', + SMS = 'sms', /** * Notifications are enqueued into an SQS queue. */ - Sqs = 'sqs', + SQS = 'sqs', /** * JSON-encoded notifications are sent to a mobile app endpoint. */ - Application = 'application', + APPLICATION = 'application', /** * Notifications trigger a Lambda function. */ - Lambda = 'lambda' + LAMBDA = 'lambda' } diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index dce05fadaa28d..cd76a274d10b4 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -2,6 +2,7 @@ import iam = require('@aws-cdk/aws-iam'); import { IResource, Resource } from '@aws-cdk/cdk'; import { TopicPolicy } from './policy'; import { ITopicSubscription } from './subscriber'; +import { Subscription } from './subscription'; export interface ITopic extends IResource { /** @@ -55,7 +56,18 @@ export abstract class TopicBase extends Resource implements ITopic { * Subscribe some endpoint to this topic */ public addSubscription(subscription: ITopicSubscription) { - subscription.bind(this, this); + const subscriptionConfig = subscription.bind(this); + + // We use the subscriber's id as the construct id. There's no meaning + // to subscribing the same subscriber twice on the same topic. + if (this.node.tryFindChild(subscriptionConfig.subscriberId)) { + throw new Error(`A subscription between the topic ${this.node.id} and the subscriber ${subscriptionConfig.subscriberId} already exists`); + } + + new Subscription(this, subscriptionConfig.subscriberId, { + topic: this, + ...subscriptionConfig, + }); } /** diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index 6747712cbc042..c42a2b1009d0a 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -1,4 +1,4 @@ -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, PhysicalName, Stack } from '@aws-cdk/cdk'; import { CfnTopic } from './sns.generated'; import { ITopic, TopicBase } from './topic-base'; @@ -22,7 +22,7 @@ export interface TopicProps { * * @default Generated name */ - readonly topicName?: string; + readonly topicName?: PhysicalName; } /** @@ -46,14 +46,24 @@ export class Topic extends TopicBase { protected readonly autoCreatePolicy: boolean = true; constructor(scope: Construct, id: string, props: TopicProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.topicName, + }); const resource = new CfnTopic(this, 'Resource', { displayName: props.displayName, - topicName: props.topicName + topicName: this.physicalName, }); - this.topicArn = resource.refAsString; - this.topicName = resource.attrTopicName; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.ref, + name: resource.attrTopicName, + arnComponents: { + service: 'sns', + resource: this.physicalName, + }, + }); + this.topicArn = resourceIdentifiers.arn; + this.topicName = resourceIdentifiers.name; } } diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index a4dc3120e4f61..16054cc704953 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -92,8 +92,9 @@ "awslint": { "exclude": [ "construct-base-is-private:@aws-cdk/aws-sns.TopicBase", - "integ-return-type:@aws-cdk/aws-sns.ITopicSubscription.bind" + "props-physical-name:@aws-cdk/aws-sns.SubscriptionProps", + "props-physical-name:@aws-cdk/aws-sns.TopicPolicyProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns.ts b/packages/@aws-cdk/aws-sns/test/integ.sns.ts index d6aaa9939ce4d..085dbdf335628 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/integ.sns.ts @@ -1,4 +1,4 @@ -import { App, Stack, StackProps } from '@aws-cdk/cdk'; +import { App, PhysicalName, Stack, StackProps } from '@aws-cdk/cdk'; import { Topic } from '../lib'; class SNSInteg extends Stack { @@ -6,7 +6,7 @@ class SNSInteg extends Stack { super(scope, id, props); new Topic(this, 'MyTopic', { - topicName: 'fooTopic', + topicName: PhysicalName.of('fooTopic'), displayName: 'fooDisplayName' }); } diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index 1ba0eb9365373..4eff0c8579c80 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -27,7 +27,7 @@ export = { const stack = new cdk.Stack(); new sns.Topic(stack, 'MyTopic', { - topicName: 'topicName' + topicName: cdk.PhysicalName.of('topicName'), }); expect(stack).toMatch({ @@ -69,7 +69,7 @@ export = { const stack = new cdk.Stack(); new sns.Topic(stack, 'MyTopic', { - topicName: 'topicName', + topicName: cdk.PhysicalName.of('topicName'), displayName: 'displayName' }); @@ -88,8 +88,6 @@ export = { test.done(); }, }, - 'subscription tests': { - }, 'can add a policy to the topic'(test: Test) { // GIVEN @@ -213,7 +211,7 @@ export = { dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, namespace: 'AWS/SNS', metricName: 'NumberOfMessagesPublished', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Sum' }); @@ -221,7 +219,7 @@ export = { dimensions: {TopicName: { 'Fn::GetAtt': [ 'TopicBFC7AF6E', 'TopicName' ] }}, namespace: 'AWS/SNS', metricName: 'PublishSize', - periodSec: 300, + period: cdk.Duration.minutes(5), statistic: 'Average' }); diff --git a/packages/@aws-cdk/aws-sns/test/test.subscription.ts b/packages/@aws-cdk/aws-sns/test/test.subscription.ts new file mode 100644 index 0000000000000..6bf653ce95f35 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/test/test.subscription.ts @@ -0,0 +1,139 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import sns = require('../lib'); + +export = { + 'create a subscription'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.LAMBDA, + topic + }); + + // THEN + expect(stack).to(haveResource('AWS::SNS::Subscription', { + Endpoint: 'endpoint', + Protocol: 'lambda', + TopicArn: { + Ref: 'TopicBFC7AF6E' + } + })); + test.done(); + }, + + 'with filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + filterPolicy: { + color: sns.SubscriptionFilter.stringFilter({ + whitelist: ['red', 'green'], + blacklist: ['white', 'orange'], + matchPrefixes: ['bl', 'ye'], + }), + price: sns.SubscriptionFilter.numericFilter({ + whitelist: [100, 200], + between: { start: 300, stop: 350 }, + greaterThan: 500, + lessThan: 1000, + betweenStrict: { start: 2000, stop: 3000 }, + greaterThanOrEqualTo: 1000, + lessThanOrEqualTo: -2, + }) + }, + protocol: sns.SubscriptionProtocol.LAMBDA, + topic + }); + + // THEN + expect(stack).to(haveResource('AWS::SNS::Subscription', { + FilterPolicy: { + color: [ + 'red', + 'green', + {'anything-but': ['white', 'orange']}, + { prefix: 'bl'}, + { prefix: 'ye'} + ], + price: [ + { numeric: ['=', 100] }, + { numeric: ['=', 200] }, + { numeric: ['>', 500] }, + { numeric: ['>=', 1000] }, + { numeric: ['<', 1000] }, + { numeric: ['<=', -2] }, + { numeric: ['>=', 300, '<=', 350] }, + { numeric: ['>', 2000, '<', 3000] }, + ] + }, + })); + test.done(); + }, + + 'throws with raw delivery for protocol other than http, https or sqs'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.LAMBDA, + topic, + rawMessageDelivery: true + }), /Raw message delivery/); + test.done(); + }, + + 'throws with more than 5 attributes in a filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + const cond = { conditions: [] }; + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.LAMBDA, + topic, + filterPolicy: { + a: cond, + b: cond, + c: cond, + d: cond, + e: cond, + f: cond, + }, + }), /5 attribute names/); + test.done(); + }, + + 'throws with more than 100 conditions in a filter policy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // THEN + test.throws(() => new sns.Subscription(stack, 'Subscription', { + endpoint: 'endpoint', + protocol: sns.SubscriptionProtocol.LAMBDA, + topic, + filterPolicy: { + a: { conditions: [...Array.from(Array(2).keys())] }, + b: { conditions: [...Array.from(Array(10).keys())] }, + c: { conditions: [...Array.from(Array(6).keys())] }, + }, + }), /\(120\) must not exceed 100/); + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index 6aa609c351ab0..b33abd4f471e8 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -1,5 +1,5 @@ import kms = require('@aws-cdk/aws-kms'); -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, PhysicalName, Stack } from '@aws-cdk/cdk'; import { IQueue, QueueAttributes, QueueBase } from './queue-base'; import { CfnQueue } from './sqs.generated'; import { validateProps } from './validate-props'; @@ -15,7 +15,7 @@ export interface QueueProps { * * @default CloudFormation-generated name */ - readonly queueName?: string; + readonly queueName?: PhysicalName; /** * The number of seconds that Amazon SQS retains a message. @@ -23,9 +23,9 @@ export interface QueueProps { * You can specify an integer value from 60 seconds (1 minute) to 1209600 * seconds (14 days). The default value is 345600 seconds (4 days). * - * @default 345600 seconds (4 days) + * @default Duration.days(4) */ - readonly retentionPeriodSec?: number; + readonly retentionPeriod?: Duration; /** * The time in seconds that the delivery of all messages in the queue is delayed. @@ -35,7 +35,7 @@ export interface QueueProps { * * @default 0 */ - readonly deliveryDelaySec?: number; + readonly deliveryDelay?: Duration; /** * The limit of how many bytes that a message can contain before Amazon SQS rejects it. @@ -57,7 +57,7 @@ export interface QueueProps { * * @default 0 */ - readonly receiveMessageWaitTimeSec?: number; + readonly receiveMessageWaitTime?: Duration; /** * Timeout of processing a single message. @@ -69,9 +69,9 @@ export interface QueueProps { * Values must be from 0 to 43200 seconds (12 hours). If you don't specify * a value, AWS CloudFormation uses the default value of 30 seconds. * - * @default 30 + * @default Duration.seconds(30) */ - readonly visibilityTimeoutSec?: number; + readonly visibilityTimeout?: Duration; /** * Send messages to this queue if they were unsuccessfully dequeued a number of times. @@ -111,9 +111,9 @@ export interface QueueProps { * The value must be an integer between 60 (1 minute) and 86,400 (24 * hours). The default is 300 (5 minutes). * - * @default 300 (5 minutes) + * @default Duration.minutes(5) */ - readonly dataKeyReuseSec?: number; + readonly dataKeyReuse?: Duration; /** * Whether this a first-in-first-out (FIFO) queue. @@ -161,19 +161,19 @@ export enum QueueEncryption { /** * Messages in the queue are not encrypted */ - Unencrypted = 'NONE', + UNENCRYPTED = 'NONE', /** * Server-side KMS encryption with a master key managed by SQS. */ - KmsManaged = 'MANAGED', + KMS_MANAGED = 'MANAGED', /** * Server-side encryption with a KMS key managed by the user. * * If `encryptionKey` is specified, this key will be used, otherwise, one will be defined. */ - Kms = 'KMS', + KMS = 'KMS', } /** @@ -230,7 +230,9 @@ export class Queue extends QueueBase { protected readonly autoCreatePolicy = true; constructor(scope: Construct, id: string, props: QueueProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.queueName, + }); validateProps(props); @@ -244,45 +246,54 @@ export class Queue extends QueueBase { const { encryptionMasterKey, encryptionProps } = _determineEncryptionProps.call(this); const queue = new CfnQueue(this, 'Resource', { - queueName: props.queueName, + queueName: this.physicalName, ...this.determineFifoProps(props), ...encryptionProps, redrivePolicy, - delaySeconds: props.deliveryDelaySec, + delaySeconds: props.deliveryDelay && props.deliveryDelay.toSeconds(), maximumMessageSize: props.maxMessageSizeBytes, - messageRetentionPeriod: props.retentionPeriodSec, - receiveMessageWaitTimeSeconds: props.receiveMessageWaitTimeSec, - visibilityTimeout: props.visibilityTimeoutSec, + messageRetentionPeriod: props.retentionPeriod && props.retentionPeriod.toSeconds(), + receiveMessageWaitTimeSeconds: props.receiveMessageWaitTime && props.receiveMessageWaitTime.toSeconds(), + visibilityTimeout: props.visibilityTimeout && props.visibilityTimeout.toSeconds(), + }); + + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: queue.attrArn, + name: queue.attrQueueName, + arnComponents: { + service: 'sqs', + resource: this.physicalName, + }, }); + this.queueArn = resourceIdentifiers.arn; + this.queueName = resourceIdentifiers.name; this.encryptionMasterKey = encryptionMasterKey; - this.queueArn = queue.attrArn; - this.queueName = queue.attrQueueName; - this.queueUrl = queue.refAsString; + this.queueUrl = queue.ref; function _determineEncryptionProps(this: Queue): { encryptionProps: EncryptionProps, encryptionMasterKey?: kms.IKey } { - let encryption = props.encryption || QueueEncryption.Unencrypted; + let encryption = props.encryption || QueueEncryption.UNENCRYPTED; - if (encryption !== QueueEncryption.Kms && props.encryptionMasterKey) { - encryption = QueueEncryption.Kms; // KMS is implied by specifying an encryption key + if (encryption !== QueueEncryption.KMS && props.encryptionMasterKey) { + encryption = QueueEncryption.KMS; // KMS is implied by specifying an encryption key } - if (encryption === QueueEncryption.Unencrypted) { + if (encryption === QueueEncryption.UNENCRYPTED) { return { encryptionProps: {} }; } - if (encryption === QueueEncryption.KmsManaged) { + if (encryption === QueueEncryption.KMS_MANAGED) { const masterKey = kms.Key.fromKeyArn(this, 'Key', 'alias/aws/sqs'); return { encryptionMasterKey: masterKey, encryptionProps: { kmsMasterKeyId: 'alias/aws/sqs', - kmsDataKeyReusePeriodSeconds: props.dataKeyReuseSec + kmsDataKeyReusePeriodSeconds: props.dataKeyReuse && props.dataKeyReuse.toSeconds() } }; } - if (encryption === QueueEncryption.Kms) { + if (encryption === QueueEncryption.KMS) { const masterKey = props.encryptionMasterKey || new kms.Key(this, 'Key', { description: `Created by ${this.node.path}` }); @@ -291,7 +302,7 @@ export class Queue extends QueueBase { encryptionMasterKey: masterKey, encryptionProps: { kmsMasterKeyId: masterKey.keyArn, - kmsDataKeyReusePeriodSeconds: props.dataKeyReuseSec + kmsDataKeyReusePeriodSeconds: props.dataKeyReuse && props.dataKeyReuse.toSeconds() } }; } @@ -306,15 +317,16 @@ export class Queue extends QueueBase { private determineFifoProps(props: QueueProps): FifoProps { // Check if any of the signals that we have say that this is a FIFO queue. let fifoQueue = props.fifo; - if (typeof fifoQueue === 'undefined' && typeof props.queueName === 'string' && props.queueName.endsWith('.fifo')) { fifoQueue = true; } + const queueName = props.queueName && props.queueName.value; + if (typeof fifoQueue === 'undefined' && typeof queueName === 'string' && queueName.endsWith('.fifo')) { fifoQueue = true; } if (typeof fifoQueue === 'undefined' && props.contentBasedDeduplication) { fifoQueue = true; } // If we have a name, see that it agrees with the FIFO setting - if (typeof props.queueName === 'string') { - if (fifoQueue && !props.queueName.endsWith('.fifo')) { + if (typeof queueName === 'string') { + if (fifoQueue && !queueName.endsWith('.fifo')) { throw new Error("FIFO queue names must end in '.fifo'"); } - if (!fifoQueue && props.queueName.endsWith('.fifo')) { + if (!fifoQueue && queueName.endsWith('.fifo')) { throw new Error("Non-FIFO queue name may not end in '.fifo'"); } } diff --git a/packages/@aws-cdk/aws-sqs/lib/validate-props.ts b/packages/@aws-cdk/aws-sqs/lib/validate-props.ts index ecebbde44d42c..3d7781fabd957 100644 --- a/packages/@aws-cdk/aws-sqs/lib/validate-props.ts +++ b/packages/@aws-cdk/aws-sqs/lib/validate-props.ts @@ -1,11 +1,11 @@ import { QueueProps } from './index'; export function validateProps(props: QueueProps) { - validateRange('delivery delay', props.deliveryDelaySec, 0, 900, 'seconds'); + validateRange('delivery delay', props.deliveryDelay && props.deliveryDelay.toSeconds(), 0, 900, 'seconds'); validateRange('maximum message size', props.maxMessageSizeBytes, 1_024, 262_144, 'bytes'); - validateRange('message retention period', props.retentionPeriodSec, 60, 1_209_600, 'seconds'); - validateRange('receive wait time', props.receiveMessageWaitTimeSec, 0, 20, 'seconds'); - validateRange('visibility timeout', props.visibilityTimeoutSec, 0, 43_200, 'seconds'); + validateRange('message retention period', props.retentionPeriod && props.retentionPeriod.toSeconds(), 60, 1_209_600, 'seconds'); + validateRange('receive wait time', props.receiveMessageWaitTime && props.receiveMessageWaitTime.toSeconds(), 0, 20, 'seconds'); + validateRange('visibility timeout', props.visibilityTimeout && props.visibilityTimeout.toSeconds(), 0, 43_200, 'seconds'); validateRange('dead letter target maximum receive count', props.deadLetterQueue && props.deadLetterQueue.maxReceiveCount, 1, +Infinity); } diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 67b21080a5d0d..5b0e9ec663950 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -89,8 +89,9 @@ }, "awslint": { "exclude": [ - "construct-base-is-private:@aws-cdk/aws-sqs.QueueBase" + "construct-base-is-private:@aws-cdk/aws-sqs.QueueBase", + "props-physical-name:@aws-cdk/aws-sqs.QueuePolicyProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index 35d22487a40f8..90650b51226d5 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import { Stack } from '@aws-cdk/cdk'; +import { Duration, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import sqs = require('../lib'); import { Queue } from '../lib'; @@ -200,7 +200,7 @@ export = { 'a kms key will be allocated if encryption = kms but a master key is not specified'(test: Test) { const stack = new Stack(); - new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.Kms }); + new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS }); expect(stack).to(haveResource('AWS::KMS::Key')); expect(stack).to(haveResource('AWS::SQS::Queue', { @@ -218,7 +218,7 @@ export = { 'it is possible to use a managed kms key'(test: Test) { const stack = new Stack(); - new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KmsManaged }); + new sqs.Queue(stack, 'Queue', { encryption: sqs.QueueEncryption.KMS_MANAGED }); expect(stack).toMatch({ "Resources": { "Queue4A7E3555": { @@ -236,7 +236,7 @@ export = { // GIVEN const stack = new Stack(); const queue = new sqs.Queue(stack, 'Queue', { - encryption: sqs.QueueEncryption.Kms + encryption: sqs.QueueEncryption.KMS }); const role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('someone') @@ -286,7 +286,7 @@ export = { dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, namespace: 'AWS/SQS', metricName: 'NumberOfMessagesSent', - periodSec: 300, + period: Duration.minutes(5), statistic: 'Sum' }); @@ -294,7 +294,7 @@ export = { dimensions: {QueueName: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'QueueName' ] }}, namespace: 'AWS/SQS', metricName: 'SentMessageSize', - periodSec: 300, + period: Duration.minutes(5), statistic: 'Average' }); diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index ccd405abef105..1fa48bddc69b5 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -1,7 +1,8 @@ import iam = require('@aws-cdk/aws-iam'); import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, - Construct, ContextProvider, Fn, IResource, Resource, Stack, Token } from '@aws-cdk/cdk'; + Construct, ContextProvider, Fn, IConstruct, IResource, PhysicalName, Resource, Stack, Token +} from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); import ssm = require('./ssm.generated'); @@ -91,7 +92,7 @@ export interface ParameterOptions { * * @default - a name will be generated by CloudFormation */ - readonly parameterName?: string; + readonly parameterName?: PhysicalName; } /** @@ -118,18 +119,10 @@ export interface StringListParameterProps extends ParameterOptions { * Basic features shared across all types of SSM Parameters. */ abstract class ParameterBase extends Resource implements IParameter { + public abstract readonly parameterArn: string; public abstract readonly parameterName: string; public abstract readonly parameterType: string; - public get parameterArn(): string { - return Stack.of(this).formatArn({ - service: 'ssm', - resource: 'parameter', - sep: '', // Sep is empty because this.parameterName starts with a / already! - resourceName: this.parameterName, - }); - } - public grantRead(grantee: iam.IGrantable): iam.Grant { return iam.Grant.addToPrincipal({ grantee, @@ -204,6 +197,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { class Import extends ParameterBase { public readonly parameterName = attrs.parameterName; + public readonly parameterArn = arnForParameterName(this, this.parameterName); public readonly parameterType = STRING_PARAM_TYPE; public readonly stringValue = stringValue; } @@ -219,6 +213,7 @@ export class StringParameter extends ParameterBase implements IStringParameter { class Import extends ParameterBase { public readonly parameterName = attrs.parameterName; + public readonly parameterArn = arnForParameterName(this, this.parameterName); public readonly parameterType = SECURE_STRING_PARAM_TYPE; public readonly stringValue = stringValue; } @@ -273,12 +268,15 @@ export class StringParameter extends ParameterBase implements IStringParameter { return this.fromSecureStringParameterAttributes(stack, id, { parameterName, version }).stringValue; } + public readonly parameterArn: string; public readonly parameterName: string; public readonly parameterType: string; public readonly stringValue: string; constructor(scope: Construct, id: string, props: StringParameterProps) { - super(scope, id); + super(scope, id, { + physicalName: props.parameterName, + }); if (props.allowedPattern) { _assertValidValue(props.stringValue, props.allowedPattern); @@ -287,12 +285,24 @@ export class StringParameter extends ParameterBase implements IStringParameter { const resource = new ssm.CfnParameter(this, 'Resource', { allowedPattern: props.allowedPattern, description: props.description, - name: props.parameterName, + name: this.physicalName, type: STRING_PARAM_TYPE, value: props.stringValue, }); - this.parameterName = resource.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForParameterName(this, resource.ref), + name: resource.ref, + arnComponents: { + service: 'ssm', + resource: 'parameter', + resourceName: this.physicalName, + sep: '', // `sep` is empty because parameterName starts with a / already! + }, + }); + this.parameterArn = resourceIdentifiers.arn; + this.parameterName = resourceIdentifiers.name; + this.parameterType = resource.attrType; this.stringValue = resource.attrValue; } @@ -310,6 +320,7 @@ export class StringListParameter extends ParameterBase implements IStringListPar public static fromStringListParameterName(scope: Construct, id: string, stringListParameterName: string): IStringListParameter { class Import extends ParameterBase { public readonly parameterName = stringListParameterName; + public readonly parameterArn = arnForParameterName(this, this.parameterName); public readonly parameterType = STRINGLIST_PARAM_TYPE; public readonly stringListValue = Fn.split(',', new CfnDynamicReference(CfnDynamicReferenceService.Ssm, stringListParameterName).toString()); } @@ -317,12 +328,15 @@ export class StringListParameter extends ParameterBase implements IStringListPar return new Import(scope, id); } + public readonly parameterArn: string; public readonly parameterName: string; public readonly parameterType: string; public readonly stringListValue: string[]; constructor(scope: Construct, id: string, props: StringListParameterProps) { - super(scope, id); + super(scope, id, { + physicalName: props.parameterName, + }); if (props.stringListValue.find(str => !Token.isUnresolved(str) && str.indexOf(',') !== -1)) { throw new Error('Values of a StringList SSM Parameter cannot contain the \',\' character. Use a string parameter instead.'); @@ -335,12 +349,23 @@ export class StringListParameter extends ParameterBase implements IStringListPar const resource = new ssm.CfnParameter(this, 'Resource', { allowedPattern: props.allowedPattern, description: props.description, - name: props.parameterName, + name: this.physicalName, type: STRINGLIST_PARAM_TYPE, value: props.stringListValue.join(','), }); + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: arnForParameterName(this, resource.ref), + name: resource.ref, + arnComponents: { + service: 'ssm', + resource: 'parameter', + resourceName: this.physicalName, + sep: '', // `sep` is empty because parameterName starts with a / already! + }, + }); + this.parameterArn = resourceIdentifiers.arn; + this.parameterName = resourceIdentifiers.name; - this.parameterName = resource.refAsString; this.parameterType = resource.attrType; this.stringListValue = Fn.split(',', resource.attrValue); } @@ -367,4 +392,13 @@ function _assertValidValue(value: string, allowedPattern: string): void { function makeIdentityForImportedValue(parameterName: string) { return `SsmParameterValue:${parameterName}:C96584B6-F00A-464E-AD19-53AFF4B05118`; -} \ No newline at end of file +} + +function arnForParameterName(scope: IConstruct, parameterName: string): string { + return Stack.of(scope).formatArn({ + service: 'ssm', + resource: 'parameter', + sep: '', // Sep is empty because this.parameterName starts with a / already! + resourceName: parameterName, + }); +} diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 892048ea63fbd..1ee2da44c6fcb 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -89,8 +89,10 @@ "import-props-interface:@aws-cdk/aws-ssm.ParameterImportProps", "resource-attribute:@aws-cdk/aws-ssm.IParameter.parameterValue", "from-attributes:fromStringParameterAttributes", - "from-attributes:fromStringListParameterAttributes" + "from-attributes:fromStringListParameterAttributes", + "props-physical-name:@aws-cdk/aws-ssm.StringListParameterProps", + "props-physical-name:@aws-cdk/aws-ssm.StringParameterProps" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts index 9066582ff5513..8fc3ad64294cd 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts @@ -7,7 +7,7 @@ class CreatingStack extends cdk.Stack { super(scope, id); new ssm.StringParameter(this, 'String', { - parameterName: '/My/Public/Parameter', + parameterName: cdk.PhysicalName.of('/My/Public/Parameter'), stringValue: 'abcdef' }); } diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts index 95fcfa71fa285..e9ba5d5c4a851 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter.ts @@ -13,7 +13,7 @@ export = { new ssm.StringParameter(stack, 'Parameter', { allowedPattern: '.*', description: 'The value Foo', - parameterName: 'FooParameter', + parameterName: cdk.PhysicalName.of('FooParameter'), stringValue: 'Foo', }); @@ -60,7 +60,7 @@ export = { new ssm.StringListParameter(stack, 'Parameter', { allowedPattern: '(Foo|Bar)', description: 'The values Foo and Bar', - parameterName: 'FooParameter', + parameterName: cdk.PhysicalName.of('FooParameter'), stringListValue: ['Foo', 'Bar'], }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index c9ebca5c49f24..910ca332e9f7e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -1,4 +1,5 @@ import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Duration } from '@aws-cdk/cdk'; /** * Properties for FunctionTask @@ -11,7 +12,7 @@ export interface InvokeActivityProps { * * @default No heart beat timeout */ - readonly heartbeatSeconds?: number; + readonly heartbeat?: Duration; } /** @@ -28,7 +29,7 @@ export class InvokeActivity implements sfn.IStepFunctionsTask { return { resourceArn: this.activity.activityArn, metricDimensions: { ActivityArn: this.activity.activityArn }, - heartbeatSeconds: this.props.heartbeatSeconds, + heartbeat: this.props.heartbeat, // No IAM permissions necessary, execution role implicitly has Activity permissions. metricPrefixSingular: 'Activity', metricPrefixPlural: 'Activities', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 3c222772e0a5b..011fe6d1c8e4e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -63,11 +63,9 @@ export class PublishToTopic implements sfn.IStepFunctionsTask { })], parameters: { TopicArn: this.topic.topicArn, - ...sfn.FieldUtils.renderObject({ - Message: this.props.message.value, - MessageStructure: this.props.messagePerSubscriptionType ? "json" : undefined, - Subject: this.props.subject, - }) + Message: this.props.message.value, + MessageStructure: this.props.messagePerSubscriptionType ? "json" : undefined, + Subject: this.props.subject, } }; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts index b51a08e08ff31..4b8250de5d892 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts @@ -102,7 +102,7 @@ export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask securityGroup?: ec2.ISecurityGroup) { if (subnetSelection === undefined) { - subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.PUBLIC : ec2.SubnetType.PRIVATE }; } // If none is given here, one will be created later on during bind() @@ -166,7 +166,7 @@ function renderOverrides(containerOverrides?: ContainerOverride[]) { const ret = new Array(); for (const override of containerOverrides) { - ret.push(sfn.FieldUtils.renderObject({ + ret.push({ Name: override.containerName, Command: override.command, Cpu: override.cpu, @@ -176,7 +176,7 @@ function renderOverrides(containerOverrides?: ContainerOverride[]) { Name: e.name, Value: e.value, })) - })); + }); } return { ContainerOverrides: ret }; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-lambda-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-lambda-task.ts index c6071227854d2..b55405537c3d6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-lambda-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-lambda-task.ts @@ -36,6 +36,13 @@ export interface RunLambdaTaskProps { * @default - No context */ readonly clientContext?: string; + + /** + * Version or alias of the function to be invoked + * + * @default - No qualifier + */ + readonly qualifier?: string; } /** @@ -75,6 +82,7 @@ export class RunLambdaTask implements sfn.IStepFunctionsTask { Payload: this.props.payload, InvocationType: this.props.invocationType, ClientContext: this.props.clientContext, + Qualifier: this.props.qualifier } }; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-task-base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-task-base-types.ts index 0a8e6bf365795..9648b4bac4928 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-task-base-types.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-task-base-types.ts @@ -1,5 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); +import { Duration } from '@aws-cdk/cdk'; // // Create Training Job types @@ -146,7 +147,7 @@ export interface StoppingCondition { /** * The maximum length of time, in seconds, that the training or compilation job can run. */ - readonly maxRuntimeInSeconds?: number; + readonly maxRuntime?: Duration; } export interface ResourceConfig { @@ -224,17 +225,17 @@ export enum S3DataType { /** * Manifest File Data Type */ - ManifestFile = 'ManifestFile', + MANIFEST_FILE = 'ManifestFile', /** * S3 Prefix Data Type */ - S3Prefix = 'S3Prefix', + S3_PREFIX = 'S3Prefix', /** * Augmented Manifest File Data Type */ - AugmentedManifestFile = 'AugmentedManifestFile' + AUGMENTED_MANIFEST_FILE = 'AugmentedManifestFile' } /** @@ -244,12 +245,12 @@ export enum S3DataDistributionType { /** * Fully replicated S3 Data Distribution Type */ - FullyReplicated = 'FullyReplicated', + FULLY_REPLICATED = 'FullyReplicated', /** * Sharded By S3 Key Data Distribution Type */ - ShardedByS3Key = 'ShardedByS3Key' + SHARDED_BY_S3_KEY = 'ShardedByS3Key' } /** @@ -259,12 +260,12 @@ export enum RecordWrapperType { /** * None record wrapper type */ - None = 'None', + NONE = 'None', /** * RecordIO record wrapper type */ - RecordIO = 'RecordIO' + RECORD_IO = 'RecordIO' } /** @@ -274,12 +275,12 @@ export enum InputMode { /** * Pipe mode */ - Pipe = 'Pipe', + PIPE = 'Pipe', /** * File mode. */ - File = 'File' + FILE = 'File' } /** @@ -289,12 +290,12 @@ export enum CompressionType { /** * None compression type */ - None = 'None', + NONE = 'None', /** * Gzip compression type */ - Gzip = 'Gzip' + GZIP = 'Gzip' } // @@ -421,12 +422,12 @@ export enum BatchStrategy { /** * Fits multiple records in a mini-batch. */ - MultiRecord = 'MultiRecord', + MULTI_RECORD = 'MultiRecord', /** * Use a single record when making an invocation request. */ - SingleRecord = 'SingleRecord' + SINGLE_RECORD = 'SingleRecord' } /** @@ -437,22 +438,22 @@ export enum SplitType { /** * Input data files are not split, */ - None = 'None', + NONE = 'None', /** * Split records on a newline character boundary. */ - Line = 'Line', + LINE = 'Line', /** * Split using MXNet RecordIO format. */ - RecordIO = 'RecordIO', + RECORD_IO = 'RecordIO', /** * Split using TensorFlow TFRecord format. */ - TFRecord = 'TFRecord' + TF_RECORD = 'TFRecord' } /** @@ -463,11 +464,11 @@ export enum AssembleWith { /** * Concatenate the results in binary format. */ - None = 'None', + NONE = 'None', /** * Add a newline character at the end of every transformed record. */ - Line = 'Line' + LINE = 'Line' -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-train-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-train-task.ts index 6059174c5eb75..4b1f38c96b7c5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-train-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-train-task.ts @@ -1,7 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import sfn = require('@aws-cdk/aws-stepfunctions'); -import { Construct, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, Stack } from '@aws-cdk/cdk'; import { AlgorithmSpecification, Channel, InputMode, OutputDataConfig, ResourceConfig, S3DataType, StoppingCondition, VpcConfig, } from './sagemaker-task-base-types'; @@ -110,13 +110,13 @@ export class SagemakerTrainTask implements ec2.IConnectable, sfn.IStepFunctionsT // set the default resource config if not defined. this.resourceConfig = props.resourceConfig || { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.XLarge), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.XLARGE), volumeSizeInGB: 10 }; // set the stopping condition if not defined this.stoppingCondition = props.stoppingCondition || { - maxRuntimeInSeconds: 3600 + maxRuntime: Duration.hours(1) }; // set the sagemaker role or create new one @@ -130,13 +130,13 @@ export class SagemakerTrainTask implements ec2.IConnectable, sfn.IStepFunctionsT // set the input mode to 'File' if not defined this.algorithmSpecification = ( props.algorithmSpecification.trainingInputMode ) ? ( props.algorithmSpecification ) : - ( { ...props.algorithmSpecification, trainingInputMode: InputMode.File } ); + ( { ...props.algorithmSpecification, trainingInputMode: InputMode.FILE } ); // set the S3 Data type of the input data config objects to be 'S3Prefix' if not defined this.inputDataConfig = props.inputDataConfig.map(config => { if (!config.dataSource.s3DataSource.s3DataType) { return Object.assign({}, config, { dataSource: { s3DataSource: - { ...config.dataSource.s3DataSource, s3DataType: S3DataType.S3Prefix } } }); + { ...config.dataSource.s3DataSource, s3DataType: S3DataType.S3_PREFIX } } }); } else { return config; } @@ -151,7 +151,7 @@ export class SagemakerTrainTask implements ec2.IConnectable, sfn.IStepFunctionsT public bind(task: sfn.Task): sfn.StepFunctionsTaskConfig { return { resourceArn: 'arn:aws:states:::sagemaker:createTrainingJob' + (this.props.synchronous ? '.sync' : ''), - parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + parameters: this.renderParameters(), policyStatements: this.makePolicyStatements(task), }; } @@ -229,7 +229,7 @@ export class SagemakerTrainTask implements ec2.IConnectable, sfn.IStepFunctionsT private renderStoppingCondition(config: StoppingCondition): {[key: string]: any} { return { StoppingCondition: { - MaxRuntimeInSeconds: config.maxRuntimeInSeconds + MaxRuntimeInSeconds: config.maxRuntime && config.maxRuntime.toSeconds() } }; } @@ -290,4 +290,4 @@ export class SagemakerTrainTask implements ec2.IConnectable, sfn.IStepFunctionsT return policyStatements; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-transform-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-transform-task.ts index 9d485f5bfadcb..8a01c6f88a2e2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-transform-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker-transform-task.ts @@ -110,7 +110,7 @@ export class SagemakerTransformTask implements sfn.IStepFunctionsTask { { transformDataSource: { s3DataSource: { ...props.transformInput.transformDataSource.s3DataSource, - s3DataType: S3DataType.S3Prefix + s3DataType: S3DataType.S3_PREFIX } } }); @@ -118,14 +118,14 @@ export class SagemakerTransformTask implements sfn.IStepFunctionsTask { // set the default value for the transform resources this.transformResources = props.transformResources || { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.XLarge), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.XLARGE), }; } public bind(task: sfn.Task): sfn.StepFunctionsTaskConfig { return { resourceArn: 'arn:aws:states:::sagemaker:createTransformJob' + (this.props.synchronous ? '.sync' : ''), - parameters: sfn.FieldUtils.renderObject(this.renderParameters()), + parameters: this.renderParameters(), policyStatements: this.makePolicyStatements(task), }; } @@ -229,4 +229,4 @@ export class SagemakerTransformTask implements sfn.IStepFunctionsTask { return policyStatements; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index 3a202c5962e0d..7bda7b2854e02 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -1,6 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Duration } from '@aws-cdk/cdk'; /** * Properties for SendMessageTask @@ -18,7 +19,7 @@ export interface SendToQueueProps { * * @default Default value of the queue is used */ - readonly delaySeconds?: number; + readonly delay?: Duration; /** * The token used for deduplication of sent messages. @@ -72,12 +73,10 @@ export class SendToQueue implements sfn.IStepFunctionsTask { })], parameters: { QueueUrl: this.queue.queueUrl, - ...sfn.FieldUtils.renderObject({ - MessageBody: this.props.messageBody.value, - DelaySeconds: this.props.delaySeconds, - MessageDeduplicationId: this.props.messageDeduplicationId, - MessageGroupId: this.props.messageGroupId, - }) + MessageBody: this.props.messageBody.value, + DelaySeconds: this.props.delay && this.props.delay.toSeconds(), + MessageDeduplicationId: this.props.messageDeduplicationId, + MessageGroupId: this.props.messageGroupId, } }; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index 31cb13d038435..42861dbecbadd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -306,7 +306,7 @@ "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" ] }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionA4A8D57E": { + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicFunctionE74B772A": { "Type": "AWS::SNS::Subscription", "Properties": { "Protocol": "lambda", @@ -978,4 +978,4 @@ "Description": "Artifact hash for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts index d7d26f48c0743..37e3594c30a22 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -20,7 +20,7 @@ const vpc = ec2.Vpc.fromLookup(stack, 'Vpc', { const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro'), - vpcSubnets: { subnetType: ec2.SubnetType.Public }, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); // Build task definition diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.expected.json index 18f710ef22d77..16f8c78de01ff 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.expected.json @@ -272,7 +272,7 @@ { "Ref": "CallbackHandler4434C38D" }, - "\",\"Payload\":{\"token\":\"$$.Task.Token\"}},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke.waitForTaskToken\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" + "\",\"Payload\":{\"token.$\":\"$$.Task.Token\"}},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke.waitForTaskToken\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" ] ] }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.ts index 2f0e1d2a797ec..2d0cb5eeaa659 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.invoke-function.ts @@ -51,7 +51,7 @@ const chain = sfn.Chain new sfn.StateMachine(stack, 'StateMachine', { definition: chain, - timeoutSec: 30 + timeout: cdk.Duration.seconds(30) }); -app.run(); \ No newline at end of file +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts index 5fae9e8d1819b..5f7f9db9d731f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts @@ -13,7 +13,7 @@ class JobPollerStack extends cdk.Stack { task: new tasks.InvokeActivity(submitJobActivity), resultPath: '$.guid', }); - const waitX = new sfn.Wait(this, 'Wait X Seconds', { duration: sfn.WaitDuration.secondsPath('$.wait_time') }); + const waitX = new sfn.Wait(this, 'Wait X Seconds', { time: sfn.WaitTime.secondsPath('$.wait_time') }); const getStatus = new sfn.Task(this, 'Get Job Status', { task: new tasks.InvokeActivity(checkJobActivity), inputPath: '$.guid', @@ -40,11 +40,11 @@ class JobPollerStack extends cdk.Stack { new sfn.StateMachine(this, 'StateMachine', { definition: chain, - timeoutSec: 30 + timeout: cdk.Duration.seconds(30) }); } } const app = new cdk.App(); new JobPollerStack(app, 'aws-stepfunctions-integ'); -app.synth(); \ No newline at end of file +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts index 611f30d627815..d9b3c8664af1d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { }); }); -test('Lambda function can be used in a Task', () => { +test('Invoke lambda with function ARN', () => { // WHEN const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); new sfn.StateMachine(stack, 'SM', { @@ -39,7 +39,7 @@ test('Lambda function payload ends up in Parameters', () => { definition: new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn, { payload: { - foo: 'bar' + foo: sfn.Data.stringAt('$.bar') } }) }) @@ -48,45 +48,10 @@ test('Lambda function payload ends up in Parameters', () => { expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { DefinitionString: { "Fn::Join": ["", [ - "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"foo\":\"bar\"},\"Type\":\"Task\",\"Resource\":\"", + "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"foo.$\":\"$.bar\"},\"Type\":\"Task\",\"Resource\":\"", { "Fn::GetAtt": ["Fn9270CBC0", "Arn"] }, "\"}}}" ]] }, }); }); - -test('Lambda function can be used in a Task with Task Token', () => { - const task = new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - waitForTaskToken: true, - payload: { - token: sfn.Context.taskToken - } - }) - }); - new sfn.StateMachine(stack, 'SM', { - definition: task - }); - - // THEN - expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - "Fn::Join": ["", [ - "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"FunctionName\":\"", - { Ref: "Fn9270CBC0" }, - "\",\"Payload\":{\"token\":\"$$.Task.Token\"}},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke.waitForTaskToken\"}}}" - ]] - }, - }); -}); - -test('Task throws if waitForTaskToken is supplied but task token is not included', () => { - expect(() => { - new sfn.Task(stack, 'Task', { - task: new tasks.RunLambdaTask(fn, { - waitForTaskToken: true - }) - }); - }).toThrow(/Task Token is missing in payload/i); -}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts index 5dbfcd9cda4ae..ca349bc0a7732 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts @@ -3,14 +3,14 @@ import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); import tasks = require('../lib'); -test('publish to SNS', () => { +test('Publish literal message to SNS topic', () => { // GIVEN const stack = new cdk.Stack(); const topic = new sns.Topic(stack, 'Topic'); // WHEN const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { - message: sfn.TaskInput.fromText('Send this message') + message: sfn.TaskInput.fromText('Publish this message') }) }); // THEN @@ -20,33 +20,72 @@ test('publish to SNS', () => { End: true, Parameters: { TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: 'Send this message' + Message: 'Publish this message' }, }); }); -test('publish JSON to SNS', () => { +test('Publish JSON to SNS topic with task token', () => { // GIVEN const stack = new cdk.Stack(); const topic = new sns.Topic(stack, 'Topic'); // WHEN const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { + waitForTaskToken: true, message: sfn.TaskInput.fromObject({ - Input: 'Send this message' + Input: 'Publish this message', + Token: sfn.Context.taskToken }) }) }); // THEN expect(stack.resolve(pub.toStateJson())).toEqual({ Type: 'Task', - Resource: 'arn:aws:states:::sns:publish', + Resource: 'arn:aws:states:::sns:publish.waitForTaskToken', End: true, Parameters: { TopicArn: { Ref: 'TopicBFC7AF6E' }, Message: { - Input: 'Send this message' + 'Input': 'Publish this message', + 'Token.$': '$$.Task.Token' } }, }); }); + +test('Task throws if waitForTaskToken is supplied but task token is not included in message', () => { + expect(() => { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + // WHEN + new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { + waitForTaskToken: true, + message: sfn.TaskInput.fromText('Publish this message') + }) }); + // THEN + }).toThrow(/Task Token is missing in message/i); +}); + +test('Publish to topic with ARN from payload', () => { + // GIVEN + const stack = new cdk.Stack(); + const topic = sns.Topic.fromTopicArn(stack, 'Topic', sfn.Data.stringAt('$.topicArn')); + + // WHEN + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { + message: sfn.TaskInput.fromText('Publish this message') + }) }); + + // THEN + expect(stack.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: 'arn:aws:states:::sns:publish', + End: true, + Parameters: { + 'TopicArn.$': '$.topicArn', + 'Message': 'Publish this message' + }, + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/run-lambda-task.test.ts new file mode 100644 index 0000000000000..1884e76c0b8b5 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/run-lambda-task.test.ts @@ -0,0 +1,77 @@ +import '@aws-cdk/assert/jest'; +import lambda = require('@aws-cdk/aws-lambda'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Stack } from '@aws-cdk/cdk'; +import tasks = require('../lib'); + +let stack: Stack; +let fn: lambda.Function; +beforeEach(() => { + stack = new Stack(); + fn = new lambda.Function(stack, 'Fn', { + code: lambda.Code.inline('hello'), + handler: 'index.hello', + runtime: lambda.Runtime.Python27, + }); +}); + +test('Invoke lambda with default magic ARN', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + payload: { + foo: 'bar' + }, + invocationType: tasks.InvocationType.RequestResponse, + clientContext: "eyJoZWxsbyI6IndvcmxkIn0=", + qualifier: "1", + }) + }); + new sfn.StateMachine(stack, 'SM', { + definition: task + }); + + expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + DefinitionString: { + "Fn::Join": ["", [ + "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"FunctionName\":\"", + { Ref: "Fn9270CBC0" }, + "\",\"Payload\":{\"foo\":\"bar\"},\"InvocationType\":\"RequestResponse\",\"ClientContext\":\"eyJoZWxsbyI6IndvcmxkIn0=\"," + + "\"Qualifier\":\"1\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke\"}}}" + ]] + }, + }); +}); + +test('Lambda function can be used in a Task with Task Token', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + waitForTaskToken: true, + payload: { + token: sfn.Context.taskToken + } + }) + }); + new sfn.StateMachine(stack, 'SM', { + definition: task + }); + + expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + DefinitionString: { + "Fn::Join": ["", [ + "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Parameters\":{\"FunctionName\":\"", + { Ref: "Fn9270CBC0" }, + "\",\"Payload\":{\"token.$\":\"$$.Task.Token\"}},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::lambda:invoke.waitForTaskToken\"}}}" + ]] + }, + }); +}); + +test('Task throws if waitForTaskToken is supplied but task token is not included in payLoad', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + waitForTaskToken: true + }) + }); + }).toThrow(/Task Token is missing in payload/i); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-training-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-training-job.test.ts index 057799772878e..ddff91b8b4bc5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-training-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-training-job.test.ts @@ -78,7 +78,7 @@ test('create complex training job', () => { const kmsKey = new kms.Key(stack, 'Key'); const vpc = new ec2.Vpc(stack, "VPC"); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc, description: 'My SG' }); - securityGroup.addIngressRule(new ec2.AnyIPv4(), new ec2.TcpPort(22), 'allow ssh access from the world'); + securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'allow ssh access from the world'); const role = new iam.Role(stack, 'Role', { assumedBy: new iam.ServicePrincipal('sagemaker.amazonaws.com'), @@ -93,7 +93,7 @@ test('create complex training job', () => { role, algorithmSpecification: { algorithmName: "BlazingText", - trainingInputMode: tasks.InputMode.File, + trainingInputMode: tasks.InputMode.FILE, metricDefinitions: [ { name: 'mymetric', regex: 'regex_pattern' @@ -107,11 +107,11 @@ test('create complex training job', () => { { channelName: "train", contentType: "image/jpeg", - compressionType: tasks.CompressionType.None, - recordWrapperType: tasks.RecordWrapperType.RecordIO, + compressionType: tasks.CompressionType.NONE, + recordWrapperType: tasks.RecordWrapperType.RECORD_IO, dataSource: { s3DataSource: { - s3DataType: tasks.S3DataType.S3Prefix, + s3DataType: tasks.S3DataType.S3_PREFIX, s3Uri: "s3://mybucket/mytrainpath", } } @@ -119,11 +119,11 @@ test('create complex training job', () => { { channelName: "test", contentType: "image/jpeg", - compressionType: tasks.CompressionType.Gzip, - recordWrapperType: tasks.RecordWrapperType.RecordIO, + compressionType: tasks.CompressionType.GZIP, + recordWrapperType: tasks.RecordWrapperType.RECORD_IO, dataSource: { s3DataSource: { - s3DataType: tasks.S3DataType.S3Prefix, + s3DataType: tasks.S3DataType.S3_PREFIX, s3Uri: "s3://mybucket/mytestpath", } } @@ -135,12 +135,12 @@ test('create complex training job', () => { }, resourceConfig: { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.P3, ec2.InstanceSize.XLarge2), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.P3, ec2.InstanceSize.XLARGE2), volumeSizeInGB: 50, volumeKmsKeyId: kmsKey, }, stoppingCondition: { - maxRuntimeInSeconds: 3600 + maxRuntime: cdk.Duration.hours(1) }, tags: { Project: "MyProject" @@ -237,14 +237,14 @@ test('pass param to training job', () => { role, algorithmSpecification: { algorithmName: "BlazingText", - trainingInputMode: tasks.InputMode.File + trainingInputMode: tasks.InputMode.FILE }, inputDataConfig: [ { channelName: 'train', dataSource: { s3DataSource: { - s3DataType: tasks.S3DataType.S3Prefix, + s3DataType: tasks.S3DataType.S3_PREFIX, s3Uri: sfn.Data.stringAt('$.S3Bucket') } } @@ -255,11 +255,11 @@ test('pass param to training job', () => { }, resourceConfig: { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.P3, ec2.InstanceSize.XLarge2), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.P3, ec2.InstanceSize.XLARGE2), volumeSizeInGB: 50 }, stoppingCondition: { - maxRuntimeInSeconds: 3600 + maxRuntime: cdk.Duration.hours(1) } })}); @@ -299,4 +299,4 @@ test('pass param to training job', () => { } }, }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-transform-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-transform-job.test.ts index f2e3aef6b55c5..d61eec4cdebc4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-transform-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker-transform-job.test.ts @@ -77,7 +77,7 @@ test('create complex transform job', () => { transformDataSource: { s3DataSource: { s3Uri: 's3://inputbucket/prefix', - s3DataType: S3DataType.S3Prefix, + s3DataType: S3DataType.S3_PREFIX, } } }, @@ -87,13 +87,13 @@ test('create complex transform job', () => { }, transformResources: { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.P3, ec2.InstanceSize.XLarge2), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.P3, ec2.InstanceSize.XLARGE2), volumeKmsKeyId: kmsKey, }, tags: { Project: 'MyProject', }, - batchStrategy: BatchStrategy.MultiRecord, + batchStrategy: BatchStrategy.MULTI_RECORD, environment: { SOMEVAR: 'myvalue' }, @@ -149,7 +149,7 @@ test('pass param to transform job', () => { transformDataSource: { s3DataSource: { s3Uri: 's3://inputbucket/prefix', - s3DataType: S3DataType.S3Prefix, + s3DataType: S3DataType.S3_PREFIX, } } }, @@ -158,7 +158,7 @@ test('pass param to transform job', () => { }, transformResources: { instanceCount: 1, - instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.P3, ec2.InstanceSize.XLarge2), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.P3, ec2.InstanceSize.XLARGE2), } }) }); @@ -187,4 +187,4 @@ test('pass param to transform job', () => { } }, }); -}); \ No newline at end of file +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts index 2407e3c6a2d41..fc0e03d0aee13 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts @@ -12,15 +12,15 @@ beforeEach(() => { queue = new sqs.Queue(stack, 'Queue'); }); -test('publish to queue', () => { +test('Send message to queue', () => { // WHEN - const pub = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { + const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromText('Send this message'), messageDeduplicationId: sfn.Data.stringAt('$.deduping'), }) }); // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(task.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -32,16 +32,52 @@ test('publish to queue', () => { }); }); -test('message body can come from state', () => { +test('Send message to SQS queue with task token', () => { // WHEN - const pub = new sfn.Task(stack, 'Send', { + const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { + waitForTaskToken: true, + messageBody: sfn.TaskInput.fromObject({ + Input: 'Send this message', + Token: sfn.Context.taskToken + }) + }) }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: 'arn:aws:states:::sqs:sendMessage.waitForTaskToken', + End: true, + Parameters: { + QueueUrl: { Ref: 'Queue4A7E3555' }, + MessageBody: { + 'Input': 'Send this message', + 'Token.$': '$$.Task.Token' + } + }, + }); +}); + +test('Task throws if waitForTaskToken is supplied but task token is not included in messageBody', () => { + expect(() => { + // WHEN + new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { + waitForTaskToken: true, + messageBody: sfn.TaskInput.fromText('Send this message') + }) }); + // THEN + }).toThrow(/Task Token is missing in messageBody/i); +}); + +test('Message body can come from state', () => { + // WHEN + const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromDataAt('$.theMessage') }) }); // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(task.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -52,9 +88,9 @@ test('message body can come from state', () => { }); }); -test('message body can be an object', () => { +test('Message body can be an object', () => { // WHEN - const pub = new sfn.Task(stack, 'Send', { + const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromObject({ literal: 'literal', @@ -64,7 +100,7 @@ test('message body can be an object', () => { }); // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(task.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, @@ -78,9 +114,9 @@ test('message body can be an object', () => { }); }); -test('message body object can contain references', () => { +test('Message body object can contain references', () => { // WHEN - const pub = new sfn.Task(stack, 'Send', { + const task = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: sfn.TaskInput.fromObject({ queueArn: queue.queueArn @@ -89,7 +125,7 @@ test('message body object can contain references', () => { }); // THEN - expect(stack.resolve(pub.toStateJson())).toEqual({ + expect(stack.resolve(task.toStateJson())).toEqual({ Type: 'Task', Resource: 'arn:aws:states:::sqs:sendMessage', End: true, diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 603971a58e6ee..ad7636cf84d26 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -73,7 +73,7 @@ const definition = submitJob new sfn.StateMachine(this, 'StateMachine', { definition, - timeoutSec: 300 + timeout: Duration.minutes(5) }); ``` @@ -148,12 +148,12 @@ similar to (for example) `inputPath`. const task = new sfn.Task(this, 'Invoke The Lambda', { task: new tasks.InvokeFunction(myLambda), inputPath: '$.input', - timeoutSeconds: 300, + timeout: Duration.minutes(5), }); // Add a retry policy task.addRetry({ - intervalSeconds: 5, + interval: Duration.seconds(5), maxAttempts: 10 }); diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 3e41384cb734e..15ae2960cf357 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,5 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { CfnActivity } from './stepfunctions.generated'; export interface ActivityProps { @@ -8,7 +8,7 @@ export interface ActivityProps { * * @default If not supplied, a name is generated */ - readonly activityName?: string; + readonly activityName?: PhysicalName; } /** @@ -52,14 +52,27 @@ export class Activity extends Resource implements IActivity { public readonly activityName: string; constructor(scope: Construct, id: string, props: ActivityProps = {}) { - super(scope, id); + super(scope, id, { + physicalName: props.activityName || + PhysicalName.of(Lazy.stringValue({ produce: () => this.generateName() })), + }); const resource = new CfnActivity(this, 'Resource', { - name: props.activityName || this.generateName() + name: this.physicalName! // not null because of above call to `super` }); - this.activityArn = resource.refAsString; - this.activityName = resource.attrName; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.ref, + name: resource.attrName, + arnComponents: { + service: 'states', + resource: 'activity', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.activityArn = resourceIdentifiers.arn; + this.activityName = resourceIdentifiers.name; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/input.ts b/packages/@aws-cdk/aws-stepfunctions/lib/input.ts index dedb89ad9af87..73273f13de457 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/input.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/input.ts @@ -10,7 +10,7 @@ export class TaskInput { * This might be a JSON-encoded object, or just a text. */ public static fromText(text: string) { - return new TaskInput(InputType.Text, text); + return new TaskInput(InputType.TEXT, text); } /** @@ -20,7 +20,7 @@ export class TaskInput { * as object values, if desired. */ public static fromObject(obj: {[key: string]: any}) { - return new TaskInput(InputType.Object, obj); + return new TaskInput(InputType.OBJECT, obj); } /** @@ -31,7 +31,7 @@ export class TaskInput { * to a task. */ public static fromDataAt(path: string) { - return new TaskInput(InputType.Text, Data.stringAt(path)); + return new TaskInput(InputType.TEXT, Data.stringAt(path)); } /** @@ -42,7 +42,7 @@ export class TaskInput { * to a task. */ public static fromContextAt(path: string) { - return new TaskInput(InputType.Text, Context.stringAt(path)); + return new TaskInput(InputType.TEXT, Context.stringAt(path)); } private constructor(public readonly type: InputType, public readonly value: any) { @@ -53,6 +53,6 @@ export class TaskInput { * The type of task input */ export enum InputType { - Text, - Object + TEXT, + OBJECT } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts index ab31ef372c37c..e9f07fa2f7a4a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts @@ -36,7 +36,8 @@ export function renderObject(obj: object | undefined): object | undefined { return recurseObject(obj, { handleString: renderString, handleList: renderStringList, - handleNumber: renderNumber + handleNumber: renderNumber, + handleBoolean: renderBoolean, }); } @@ -63,6 +64,10 @@ export function findReferencedPaths(obj: object | undefined): Set { const path = jsonPathNumber(x); if (path !== undefined) { found.add(path); } return {}; + }, + + handleBoolean(_key: string, _x: boolean) { + return {}; } }); @@ -73,6 +78,7 @@ interface FieldHandlers { handleString(key: string, x: string): {[key: string]: string}; handleList(key: string, x: string[]): {[key: string]: string[] | string }; handleNumber(key: string, x: number): {[key: string]: number | string}; + handleBoolean(key: string, x: boolean): {[key: string]: boolean}; } export function recurseObject(obj: object | undefined, handlers: FieldHandlers): object | undefined { @@ -86,6 +92,8 @@ export function recurseObject(obj: object | undefined, handlers: FieldHandlers): Object.assign(ret, handlers.handleNumber(key, value)); } else if (Array.isArray(value)) { Object.assign(ret, recurseArray(key, value, handlers)); + } else if (typeof value === 'boolean') { + Object.assign(ret, handlers.handleBoolean(key, value)); } else if (value === null || value === undefined) { // Nothing } else if (typeof value === 'object') { @@ -144,7 +152,7 @@ function renderString(key: string, value: string): {[key: string]: string} { } /** - * Render a parameter string + * Render a parameter string list * * If the string value starts with '$.', render it as a path string, otherwise as a direct string. */ @@ -158,7 +166,7 @@ function renderStringList(key: string, value: string[]): {[key: string]: string[ } /** - * Render a parameter string + * Render a parameter number * * If the string value starts with '$.', render it as a path string, otherwise as a direct string. */ @@ -171,6 +179,13 @@ function renderNumber(key: string, value: number): {[key: string]: number | stri } } +/** + * Render a parameter boolean + */ +function renderBoolean(key: string, value: boolean): {[key: string]: boolean} { + return { [key]: value }; +} + /** * If the indicated string is an encoded JSON path, return the path * diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-graph.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-graph.ts index 50d368493ce63..00f13f69da502 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-graph.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-graph.ts @@ -1,4 +1,5 @@ import iam = require('@aws-cdk/aws-iam'); +import { Duration } from '@aws-cdk/cdk'; import { State } from "./states/state"; /** @@ -32,7 +33,7 @@ export class StateGraph { * * @default No timeout */ - public timeoutSeconds?: number; + public timeout?: Duration; /** * The accumulated policy statements @@ -107,7 +108,7 @@ export class StateGraph { return { StartAt: this.startState.stateId, States: states, - TimeoutSeconds: this.timeoutSeconds + TimeoutSeconds: this.timeout && this.timeout.toSeconds() }; } @@ -154,4 +155,4 @@ export class StateGraph { } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 202e235e49c41..74260bb2a220e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -1,6 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); -import { Construct, IResource, Resource, Stack } from '@aws-cdk/cdk'; +import { Construct, Duration, IResource, PhysicalName, Resource, Stack } from '@aws-cdk/cdk'; import { StateGraph } from './state-graph'; import { CfnStateMachine } from './stepfunctions.generated'; import { IChainable } from './types'; @@ -14,7 +14,7 @@ export interface StateMachineProps { * * @default A name is automatically generated */ - readonly stateMachineName?: string; + readonly stateMachineName?: PhysicalName; /** * Definition for this state machine @@ -33,7 +33,7 @@ export interface StateMachineProps { * * @default No timeout */ - readonly timeoutSec?: number; + readonly timeout?: Duration; } /** @@ -87,17 +87,19 @@ export class StateMachine extends StateMachineBase { public readonly stateMachineArn: string; constructor(scope: Construct, id: string, props: StateMachineProps) { - super(scope, id); + super(scope, id, { + physicalName: props.stateMachineName, + }); this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal(`states.${Stack.of(this).region}.amazonaws.com`), }); const graph = new StateGraph(props.definition.startState, `State Machine ${id} definition`); - graph.timeoutSeconds = props.timeoutSec; + graph.timeout = props.timeout; const resource = new CfnStateMachine(this, 'Resource', { - stateMachineName: props.stateMachineName, + stateMachineName: this.physicalName, roleArn: this.role.roleArn, definitionString: Stack.of(this).toJsonString(graph.toGraphJson()), }); @@ -106,8 +108,18 @@ export class StateMachine extends StateMachineBase { this.addToRolePolicy(statement); } - this.stateMachineName = resource.attrName; - this.stateMachineArn = resource.refAsString; + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ + arn: resource.ref, + name: resource.attrName, + arnComponents: { + service: 'states', + resource: 'stateMachine', + resourceName: this.physicalName, + sep: ':', + }, + }); + this.stateMachineName = resourceIdentifiers.name; + this.stateMachineArn = resourceIdentifiers.arn; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index 5dce6925b53d7..edbabaddb9712 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -437,7 +437,7 @@ interface CatchTransition { function renderRetry(retry: RetryProps) { return { ErrorEquals: retry.errors, - IntervalSeconds: retry.intervalSeconds, + IntervalSeconds: retry.interval && retry.interval.toSeconds(), MaxAttempts: retry.maxAttempts, BackoffRate: retry.backoffRate }; @@ -507,4 +507,4 @@ export enum StateType { Succeed = 'Succeed', Fail = 'Fail', Parallel = 'Parallel' -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index e39fd94561b83..51f2833753a28 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -1,6 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { Chain } from '../chain'; +import { FieldUtils } from '../fields'; import { StateGraph } from '../state-graph'; import { IStepFunctionsTask, StepFunctionsTaskConfig } from '../step-functions-task'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; @@ -59,7 +60,7 @@ export interface TaskProps { * * @default 60 */ - readonly timeoutSeconds?: number; + readonly timeout?: cdk.Duration; } /** @@ -74,13 +75,13 @@ export interface TaskProps { */ export class Task extends State implements INextable { public readonly endStates: INextable[]; - private readonly timeoutSeconds?: number; + private readonly timeout?: cdk.Duration; private readonly taskProps: StepFunctionsTaskConfig; constructor(scope: cdk.Construct, id: string, props: TaskProps) { super(scope, id, props); - this.timeoutSeconds = props.timeoutSeconds; + this.timeout = props.timeout; this.taskProps = props.task.bind(this); this.endStates = [this]; } @@ -126,10 +127,10 @@ export class Task extends State implements INextable { Type: StateType.Task, Comment: this.comment, Resource: this.taskProps.resourceArn, - Parameters: this.taskProps.parameters, + Parameters: this.taskProps.parameters && FieldUtils.renderObject(this.taskProps.parameters), ResultPath: renderJsonPath(this.resultPath), - TimeoutSeconds: this.timeoutSeconds, - HeartbeatSeconds: this.taskProps.heartbeatSeconds, + TimeoutSeconds: this.timeout && this.timeout.toSeconds(), + HeartbeatSeconds: this.taskProps.heartbeat && this.taskProps.heartbeat.toSeconds(), }; } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts index 608714ad80c1b..87be700236972 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts @@ -3,32 +3,32 @@ import { Chain } from '../chain'; import { IChainable, INextable } from '../types'; import { State, StateType } from './state'; -export class WaitDuration { +export class WaitTime { /** - * Wait a fixed number of seconds + * Wait a fixed amount of time. */ - public static seconds(duration: number) { return new WaitDuration({ Seconds: duration }); } + public static duration(duration: cdk.Duration) { return new WaitTime({ Seconds: duration.toSeconds() }); } /** * Wait until the given ISO8601 timestamp * * @example 2016-03-14T01:59:00Z */ - public static timestamp(timestamp: string) { return new WaitDuration({ Timestamp: timestamp }); } + public static timestamp(timestamp: string) { return new WaitTime({ Timestamp: timestamp }); } /** * Wait for a number of seconds stored in the state object. * * @example $.waitSeconds */ - public static secondsPath(path: string) { return new WaitDuration({ SecondsPath: path }); } + public static secondsPath(path: string) { return new WaitTime({ SecondsPath: path }); } /** * Wait until a timestamp found in the state object. * * @example $.waitTimestamp */ - public static timestampPath(path: string) { return new WaitDuration({ TimestampPath: path }); } + public static timestampPath(path: string) { return new WaitTime({ TimestampPath: path }); } private constructor(private readonly json: any) { } @@ -54,7 +54,7 @@ export interface WaitProps { /** * Wait duration. */ - readonly duration: WaitDuration; + readonly time: WaitTime; } /** @@ -65,12 +65,12 @@ export interface WaitProps { export class Wait extends State implements INextable { public readonly endStates: INextable[]; - private readonly duration: WaitDuration; + private readonly time: WaitTime; constructor(scope: cdk.Construct, id: string, props: WaitProps) { super(scope, id, props); - this.duration = props.duration; + this.time = props.time; this.endStates = [this]; } @@ -89,8 +89,8 @@ export class Wait extends State implements INextable { return { Type: StateType.Wait, Comment: this.comment, - ...this.duration._json, + ...this.time._json, ...this.renderNextEnd(), }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts index 6a7a756df74b0..a8dabc6658f54 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts @@ -1,5 +1,6 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); +import { Duration } from '@aws-cdk/cdk'; import { Task } from './states/task'; /** @@ -47,7 +48,7 @@ export interface StepFunctionsTaskConfig { * * @default No heart beat timeout */ - readonly heartbeatSeconds?: number; + readonly heartbeat?: Duration; /** * Additional policy statements to add to the execution role diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts index b6e19a127ba98..5116c0382db38 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts @@ -1,3 +1,4 @@ +import { Duration } from '@aws-cdk/cdk'; import { Chain } from './chain'; import { State } from './states/state'; @@ -93,9 +94,9 @@ export interface RetryProps { /** * How many seconds to wait initially before retrying * - * @default 1 + * @default Duration.seconds(1) */ - readonly intervalSeconds?: number; + readonly interval?: Duration; /** * How many times to retry this particular error. @@ -143,4 +144,4 @@ export interface CatchProps { /** * Special string value to discard state input, output or result */ -export const DISCARD = 'DISCARD'; \ No newline at end of file +export const DISCARD = 'DISCARD'; diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index 7db195bfca578..a357bb7ef1cd0 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -88,8 +88,10 @@ "awslint": { "exclude": [ "import-props-interface:@aws-cdk/aws-stepfunctions.ActivityImportProps", - "export:@aws-cdk/aws-stepfunctions.IActivity" + "export:@aws-cdk/aws-stepfunctions.IActivity", + "duration-prop-type:@aws-cdk/aws-stepfunctions.WaitProps.duration", + "duration-prop-type:@aws-cdk/aws-stepfunctions.Errors.Timeout" ] }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts index 5b45bdee74467..f0775cc2e4168 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts @@ -28,7 +28,7 @@ export = { // THEN const sharedMetric = { - periodSec: 300, + period: cdk.Duration.minutes(5), namespace: 'AWS/States', dimensions: { ActivityArn: { Ref: 'Activity04690B0A' }}, }; @@ -46,4 +46,4 @@ export = { test.done(); } -}; \ No newline at end of file +}; diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.fields.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.fields.ts index 737ad04d43ec3..575b0d33d72e7 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.fields.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.fields.ts @@ -4,6 +4,8 @@ import { Context, Data, FieldUtils } from "../lib"; export = { 'deep replace correctly handles fields in arrays'(test: Test) { test.deepEqual(FieldUtils.renderObject({ + unknown: undefined, + bool: true, literal: 'literal', field: Data.stringAt('$.stringField'), listField: Data.listAt('$.listField'), @@ -14,6 +16,7 @@ export = { } ] }), { + 'bool': true, 'literal': 'literal', 'field.$': '$.stringField', 'listField.$': '$.listField', @@ -33,10 +36,12 @@ export = { str: Context.stringAt('$$.Execution.StartTime'), count: Context.numberAt('$$.State.RetryCount'), token: Context.taskToken, + entire: Context.entireContext }), { 'str.$': '$$.Execution.StartTime', 'count.$': '$$.State.RetryCount', - 'token.$': '$$.Task.Token' + 'token.$': '$$.Task.Token', + 'entire.$': '$$' }); test.done(); @@ -44,6 +49,7 @@ export = { 'find all referenced paths'(test: Test) { test.deepEqual(FieldUtils.findReferencedPaths({ + bool: false, literal: 'literal', field: Data.stringAt('$.stringField'), listField: Data.listAt('$.listField'), diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index edc794c6f126b..a4c2d50e5c45b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -102,7 +102,9 @@ export = { const task1 = new stepfunctions.Pass(stack, 'State One'); const task2 = new stepfunctions.Pass(stack, 'State Two'); - const task3 = new stepfunctions.Wait(stack, 'State Three', { duration: stepfunctions.WaitDuration.seconds(10) }); + const task3 = new stepfunctions.Wait(stack, 'State Three', { + time: stepfunctions.WaitTime.duration(cdk.Duration.seconds(10)) + }); // WHEN const chain = stepfunctions.Chain diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index 8c52b1b84539f..dec5fe1720128 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { CloudAssembly } from '@aws-cdk/cx-api'; import { Construct, ConstructNode } from './construct'; -import { collectRuntimeInformation } from './runtime-info'; +import { collectRuntimeInformation } from './private/runtime-info'; const APP_SYMBOL = Symbol.for('@aws-cdk/cdk.App'); @@ -131,13 +131,6 @@ export class App extends Construct { return assembly; } - /** - * @deprecated use `synth()` - */ - public run() { - return this.synth(); - } - private loadContext(defaults: { [key: string]: string } = { }) { // prime with defaults passed through constructor for (const [ k, v ] of Object.entries(defaults)) { diff --git a/packages/@aws-cdk/cdk/lib/arn.ts b/packages/@aws-cdk/cdk/lib/arn.ts index 2a74dbb0f89f9..31a6a22106d65 100644 --- a/packages/@aws-cdk/cdk/lib/arn.ts +++ b/packages/@aws-cdk/cdk/lib/arn.ts @@ -1,4 +1,4 @@ -import { Fn } from './fn'; +import { Fn } from './cfn-fn'; import { Stack } from './stack'; import { Token } from './token'; import { filterUndefined } from './util'; diff --git a/packages/@aws-cdk/cdk/lib/cfn-condition.ts b/packages/@aws-cdk/cdk/lib/cfn-condition.ts index d232ce7ed21c4..492bd2dbc0e3c 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-condition.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-condition.ts @@ -16,8 +16,6 @@ export interface CfnConditionProps { * the determination must be made at deploy time. */ export class CfnCondition extends CfnElement implements ICfnConditionExpression, IResolvable { - public readonly displayHint: string | undefined; - /** * The condition statement. */ @@ -29,7 +27,6 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, */ constructor(scope: Construct, id: string, props?: CfnConditionProps) { super(scope, id); - this.displayHint = `Cond${id}`; this.expression = props && props.expression; } @@ -79,29 +76,14 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, * capabilities; however, for the test environment, you want to use less * capabilities to save costs. With conditions, you can define which resources * are created and how they're configured for each environment type. + * + * You can use `toString` when you wish to embed a condition expression + * in a property value that accepts a `string`. For example: + * + * ```ts + * new sqs.Queue(this, 'MyQueue', { + * queueName: Fn.conditionIf('Condition', 'Hello', 'World').toString() + * }); + * ``` */ -export interface ICfnConditionExpression { - /** - * Returns a JSON node that represents this condition expression - */ - resolve(context: IResolveContext): any; - - /** - * Returns a string token representation of this condition expression, which - * resolves to the CloudFormation condition JSON during synthesis. - * - * You can use `toString` when you wish to embed a condition expression - * in a property value that accepts a `string`. For example: - * - * ```ts - * new sqs.Queue(this, 'MyQueue', { - * queueName: Fn.conditionIf('Condition', 'Hello', 'World').toString() - * }); - * ``` - * - * NOTE: we need this explicitly here despite the fact that in JavaScript this would - * "just work" since conditions are eventually tokens that implement `toString`, - * in order for jsii languages like Java to proxy this to jsii. - */ - toString(): string; -} +export interface ICfnConditionExpression extends IResolvable { } diff --git a/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts b/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts index f545f77aa62eb..9727d7356a821 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts @@ -3,7 +3,7 @@ import { Intrinsic } from "./private/intrinsic"; /** * Properties for a Dynamic Reference */ -export interface DynamicReferenceProps { +export interface CfnDynamicReferenceProps { /** * The service to retrieve the dynamic reference from */ diff --git a/packages/@aws-cdk/cdk/lib/cfn-element.ts b/packages/@aws-cdk/cdk/lib/cfn-element.ts index 25014794a8c10..4b24d7560e757 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-element.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-element.ts @@ -164,17 +164,13 @@ export abstract class CfnElement extends Construct { */ export abstract class CfnRefElement extends CfnElement { /** - * Return a token that will CloudFormation { Ref } this stack element - */ - public get ref(): IResolvable { - return CfnReference.for(this, 'Ref'); - } - - /** - * Return a string that will CloudFormation { Ref } this stack element + * Return a string that will be resolved to a CloudFormation `{ Ref }` for this element. + * + * If, by any chance, the intrinsic reference of a resource is not a string, you could + * coerce it to an IResolvable through `Lazy.any({ produce: resource.ref })`. */ - public get refAsString(): string { - return Token.asString(this.ref); + public get ref(): string { + return Token.asString(CfnReference.for(this, 'Ref')); } } @@ -185,5 +181,4 @@ function notTooLong(x: string) { import { CfnReference } from "./private/cfn-reference"; import { findTokens } from "./private/resolve"; -import { IResolvable } from "./resolvable"; import { Stack } from './stack'; diff --git a/packages/@aws-cdk/cdk/lib/fn.ts b/packages/@aws-cdk/cdk/lib/cfn-fn.ts similarity index 97% rename from packages/@aws-cdk/cdk/lib/fn.ts rename to packages/@aws-cdk/cdk/lib/cfn-fn.ts index 41a03d589c5af..fc61456a7050c 100644 --- a/packages/@aws-cdk/cdk/lib/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-fn.ts @@ -1,5 +1,5 @@ import { ICfnConditionExpression } from './cfn-condition'; -import { minimalCloudFormationJoin } from './cloudformation-lang'; +import { minimalCloudFormationJoin } from './private/cloudformation-lang'; import { Intrinsic } from './private/intrinsic'; import { IResolvable, IResolveContext } from './resolvable'; import { captureStackTrace } from './stack-trace'; @@ -240,7 +240,7 @@ export class Fn { * of strings. * @returns an FnCondition token */ - public conditionEachMemberEquals(listOfStrings: string[], value: string): ICfnConditionExpression { + public static conditionEachMemberEquals(listOfStrings: string[], value: string): ICfnConditionExpression { return new FnEachMemberEquals(listOfStrings, value); } @@ -255,7 +255,7 @@ export class Fn { * strings_to_check parameter. * @returns an FnCondition token */ - public conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string): ICfnConditionExpression { + public static conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string[]): ICfnConditionExpression { return new FnEachMemberIn(stringsToCheck, stringsToMatch); } @@ -266,7 +266,7 @@ export class Fn { * Parameters in the AWS CloudFormation User Guide. * @returns a token represented as a string array */ - public refAll(parameterType: string): string[] { + public static refAll(parameterType: string): string[] { return Token.asList(new FnRefAll(parameterType)); } @@ -280,7 +280,7 @@ export class Fn { * value. * @returns a token represented as a string */ - public valueOf(parameterOrLogicalId: string, attribute: string): string { + public static valueOf(parameterOrLogicalId: string, attribute: string): string { return new FnValueOf(parameterOrLogicalId, attribute).toString(); } @@ -294,9 +294,11 @@ export class Fn { * value. For more information about attributes, see Supported Attributes. * @returns a token represented as a string array */ - public valueOfAll(parameterType: string, attribute: string): string[] { + public static valueOfAll(parameterType: string, attribute: string): string[] { return Token.asList(new FnValueOfAll(parameterType, attribute)); } + + private constructor() { } } /** @@ -577,8 +579,8 @@ class FnEachMemberIn extends FnConditionBase { * @param stringsToCheck A list of strings, such as "A", "B", "C". AWS CloudFormation checks whether each member in the strings_to_check parameter is in the strings_to_match parameter. * @param stringsToMatch A list of strings, such as "A", "B", "C". Each member in the strings_to_match parameter is compared against the members of the strings_to_check parameter. */ - constructor(stringsToCheck: any, stringsToMatch: any) { - super('Fn::EachMemberIn', [ [stringsToCheck], stringsToMatch ]); + constructor(stringsToCheck: string[], stringsToMatch: string[]) { + super('Fn::EachMemberIn', [stringsToCheck, stringsToMatch]); } } @@ -685,4 +687,4 @@ class FnJoin implements IResolvable { const resolvedValues = this.listOfValues.map(context.resolve); return this._resolvedValues = minimalCloudFormationJoin(this.delimiter, resolvedValues); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/lib/include.ts b/packages/@aws-cdk/cdk/lib/cfn-include.ts similarity index 87% rename from packages/@aws-cdk/cdk/lib/include.ts rename to packages/@aws-cdk/cdk/lib/cfn-include.ts index b63e7cff1493e..667c31ad34c23 100644 --- a/packages/@aws-cdk/cdk/lib/include.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-include.ts @@ -1,7 +1,7 @@ import { CfnElement } from './cfn-element'; import { Construct } from './construct'; -export interface IncludeProps { +export interface CfnIncludeProps { /** * The CloudFormation template to include in the stack (as is). */ @@ -12,7 +12,7 @@ export interface IncludeProps { * Includes a CloudFormation template into a stack. All elements of the template will be merged into * the current stack, together with any elements created programmatically. */ -export class Include extends CfnElement { +export class CfnInclude extends CfnElement { /** * The included template. */ @@ -26,7 +26,7 @@ export class Include extends CfnElement { * @param id The ID of this construct * @param template The template to adopt. */ - constructor(scope: Construct, id: string, props: IncludeProps) { + constructor(scope: Construct, id: string, props: CfnIncludeProps) { super(scope, id); this.template = props.template; } diff --git a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts index d02a435bc3024..cdafb76ddb27e 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts @@ -1,6 +1,6 @@ import { CfnRefElement } from './cfn-element'; +import { Fn } from './cfn-fn'; import { Construct } from './construct'; -import { Fn } from './fn'; import { Token } from './token'; export interface CfnMappingProps { diff --git a/packages/@aws-cdk/cdk/lib/cfn-output.ts b/packages/@aws-cdk/cdk/lib/cfn-output.ts index e5f2a764d777d..ecc8fd03bb7bb 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-output.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-output.ts @@ -15,33 +15,20 @@ export interface CfnOutputProps { * The value of an output can include literals, parameter references, pseudo-parameters, * a mapping value, or intrinsic functions. */ - readonly value: any; + readonly value: string; /** * The name used to export the value of this output across stacks. * - * To import the value from another stack, use `FnImportValue(export)`. You - * can create an import value token by calling `output.makeImportValue()`. + * To import the value from another stack, use `Fn.importValue(exportName)`. * - * @default - Automatically allocate a name when `makeImportValue()` is - * called. + * @default - the output is not exported */ - readonly export?: string; + readonly exportName?: string; /** - * Disables the automatic allocation of an export name for this output. - * - * This prohibits exporting this value, either by specifying `export` or - * by calling `makeImportValue()`. - * - * @default false - */ - readonly disableExport?: boolean; - - /** - * A condition from the "Conditions" section to associate with this output - * value. If the condition evaluates to `false`, this output value will not - * be included in the stack. + * A condition to associate with this output value. If the condition evaluates + * to `false`, this output value will not be included in the stack. * * @default - No condition is associated with the output. */ @@ -49,30 +36,10 @@ export interface CfnOutputProps { } export class CfnOutput extends CfnElement { - /** - * A String type that describes the output value. - * The description can be a maximum of 4 K in length. - */ - public readonly description?: string; - - /** - * The name of the resource output to be exported for a cross-stack reference. - * By default, the logical ID of the CfnOutput element is used as it's export name. - * - * May be undefined if the CfnOutput hasn't been exported yet. - */ - public export?: string; - - /** - * A condition from the "Conditions" section to associate with this output - * value. If the condition evaluates to `false`, this output value will not - * be included in the stack. - */ - public readonly condition?: CfnCondition; - - private _value?: any; - - private disableExport: boolean; + private readonly _description?: string; + private readonly _condition?: CfnCondition; + private readonly _value?: any; + private readonly _export?: string; /** * Creates an CfnOutput value for this stack. @@ -86,37 +53,10 @@ export class CfnOutput extends CfnElement { throw new Error(`Missing value for CloudFormation output at path "${this.node.path}"`); } - this.description = props.description; + this._description = props.description; this._value = props.value; - this.condition = props.condition; - - this.disableExport = props.disableExport !== undefined ? props.disableExport : false; - - if (props.export && this.disableExport) { - throw new Error('Cannot set `disableExport` and specify an export name'); - } - - this.export = props.export; - - if (props.export) { - this.export = props.export; - } - } - - /** - * The value of the property returned by the aws cloudformation describe-stacks command. - * The value of an output can include literals, parameter references, pseudo-parameters, - * a mapping value, or intrinsic functions. - */ - public get value(): any { - return this._value; - } - - /** - * Returns an FnImportValue bound to this export name. - */ - public makeImportValue() { - return fn().importValue(this.obtainExportName()); + this._condition = props.condition; + this._export = props.exportName; } /** @@ -126,150 +66,14 @@ export class CfnOutput extends CfnElement { return { Outputs: { [this.logicalId]: { - Description: this.description, - Value: this.value, - Export: this.export != null ? { Name: this.export } : undefined, - Condition: this.condition ? this.condition.logicalId : undefined + Description: this._description, + Value: this._value, + Export: this._export != null ? { Name: this._export } : undefined, + Condition: this._condition ? this._condition.logicalId : undefined } } }; } - - /** - * Allocate an export name for this `CfnOutput` if not already done. - */ - public obtainExportName(): string { - if (!this.export && this.disableExport) { - throw new Error('Cannot create an ImportValue; `disableExport` has been set.'); - } - if (!this.export) { - this.export = this.uniqueOutputName(); - } - return this.export; - } - - /** - * Automatically determine an output name for use with FnImportValue - * - * This gets called in case the user hasn't specified an export name but is - * taking an action that requires exporting. We namespace with the stack name - * to reduce chances of collissions between CDK apps. - */ - private uniqueOutputName() { - // prefix export name with stack name since exports are global within account + region. - const stackName = this.stack.stackName; - return (stackName ? stackName + ':' : '') + this.logicalId; - } -} - -/** - * Properties for ListOutput - */ -export interface StringListCfnOutputProps { - /** - * A String type that describes the output value. - * The description can be a maximum of 4 K in length. - * - * @default - No description. - */ - readonly description?: string; - - /** - * The list of primitives to export - */ - readonly values: any[]; - - /** - * The separator to use to separate stringified values - * - * @default "," - */ - readonly separator?: string; - - /** - * The name used to export the value of this output across stacks. To import - * the value from another stack, use `FnImportValue(export)`. You can create - * an import value token by calling `output.makeImportValue()`. - * - * @default The default behavior is to automatically allocate an export name - * for outputs based on the stack name and the output's logical ID. To - * create an output without an export, set `disableExport: true`. - */ - readonly export?: string; - - /** - * Disables the automatic allocation of an export name for this output. - * - * @default false, which means that an export name is either explicitly - * specified or allocated based on the output's logical ID and stack name. - */ - readonly disableExport?: boolean; - - /** - * A condition from the "Conditions" section to associate with this output - * value. If the condition evaluates to `false`, this output value will not - * be included in the stack. - * - * @default - None. - */ - readonly condition?: CfnCondition; -} - -/** - * An output for a list of strings. - * - * Exports a list of Tokens via an CfnOutput variable, and return a list of Tokens - * that selects the imported values for them. - */ -export class StringListCfnOutput extends Construct { - /** - * Number of elements in the stringlist - */ - public readonly length: number; - - /** - * The separator used to combine the string values - */ - private readonly separator: string; - - /** - * The CfnOutput object that was created - */ - private readonly output: CfnOutput; - - constructor(scope: Construct, id: string, props: StringListCfnOutputProps) { - super(scope, id); - - this.separator = props.separator || ','; - this.length = props.values.length; - - this.output = new CfnOutput(this, 'Resource', { - description: props.description, - condition: props.condition, - disableExport: props.disableExport, - export: props.export, - value: fn().join(this.separator, props.values) - }); - } - - /** - * Return an array of imported values for this CfnOutput - */ - public makeImportValues(): string[] { - const combined = this.output.makeImportValue(); - - const ret = []; - for (let i = 0; i < this.length; i++) { - ret.push(fn().select(i, fn().split(this.separator, combined))); - } - - return ret; - } -} - -function fn() { - // Lazy loading of "Fn" module to break dependency cycles on startup - return require('./fn').Fn; } import { CfnCondition } from './cfn-condition'; diff --git a/packages/@aws-cdk/cdk/lib/cfn-parameter.ts b/packages/@aws-cdk/cdk/lib/cfn-parameter.ts index 15004490a079f..7a8d3ae6845ca 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-parameter.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-parameter.ts @@ -90,22 +90,15 @@ export interface CfnParameterProps { } /** + * A CloudFormation parameter. + * * Use the optional Parameters section to customize your templates. * Parameters enable you to input custom values to your template each time you create or * update a stack. */ -export class CfnParameter extends CfnElement implements IResolvable { - public readonly displayHint: string | undefined; - - /** - * Indicates if this parameter has "NoEcho" set. - */ - public readonly noEcho: boolean; - +export class CfnParameter extends CfnElement { private readonly type: string; - private properties: CfnParameterProps; - /** * Creates a parameter construct. * Note that the name (logical ID) of the parameter will derive from it's `coname` and location @@ -114,12 +107,17 @@ export class CfnParameter extends CfnElement implements IResolvable { * @param scope The parent construct. * @param props The parameter properties. */ - constructor(scope: Construct, id: string, props: CfnParameterProps = {}) { + constructor(scope: Construct, id: string, private readonly props: CfnParameterProps = {}) { super(scope, id); + this.type = props.type || 'String'; - this.displayHint = `Param${id}`; - this.properties = props; - this.noEcho = props.noEcho || false; + } + + /** + * Indicates if this parameter is configured with "NoEcho" enabled. + */ + public get noEcho(): boolean { + return !!this.props.noEcho; } /** @@ -167,16 +165,16 @@ export class CfnParameter extends CfnElement implements IResolvable { Parameters: { [this.logicalId]: { Type: this.type, - Default: this.properties.default, - AllowedPattern: this.properties.allowedPattern, - AllowedValues: this.properties.allowedValues, - ConstraintDescription: this.properties.constraintDescription, - Description: this.properties.description, - MaxLength: this.properties.maxLength, - MaxValue: this.properties.maxValue, - MinLength: this.properties.minLength, - MinValue: this.properties.minValue, - NoEcho: this.properties.noEcho + Default: this.props.default, + AllowedPattern: this.props.allowedPattern, + AllowedValues: this.props.allowedValues, + ConstraintDescription: this.props.constraintDescription, + Description: this.props.description, + MaxLength: this.props.maxLength, + MaxValue: this.props.maxValue, + MinLength: this.props.minLength, + MinValue: this.props.minValue, + NoEcho: this.props.noEcho } } }; diff --git a/packages/@aws-cdk/cdk/lib/pseudo.ts b/packages/@aws-cdk/cdk/lib/cfn-pseudo.ts similarity index 100% rename from packages/@aws-cdk/cdk/lib/pseudo.ts rename to packages/@aws-cdk/cdk/lib/cfn-pseudo.ts diff --git a/packages/@aws-cdk/cdk/lib/resource-policy.ts b/packages/@aws-cdk/cdk/lib/cfn-resource-policy.ts similarity index 94% rename from packages/@aws-cdk/cdk/lib/resource-policy.ts rename to packages/@aws-cdk/cdk/lib/cfn-resource-policy.ts index a3ff6b9d84691..c7f65d39ef881 100644 --- a/packages/@aws-cdk/cdk/lib/resource-policy.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-resource-policy.ts @@ -14,25 +14,25 @@ * signal to the instance after the applications are installed and configured. For a detailed example, see Deploying Applications * on Amazon EC2 with AWS CloudFormation. */ -export interface CreationPolicy { +export interface CfnCreationPolicy { /** * For an Auto Scaling group replacement update, specifies how many instances must signal success for the * update to succeed. */ - readonly autoScalingCreationPolicy?: AutoScalingCreationPolicy; + readonly autoScalingCreationPolicy?: CfnResourceAutoScalingCreationPolicy; /** * When AWS CloudFormation creates the associated resource, configures the number of required success signals and * the length of time that AWS CloudFormation waits for those signals. */ - readonly resourceSignal?: ResourceSignal; + readonly resourceSignal?: CfnResourceSignal; } /** * For an Auto Scaling group replacement update, specifies how many instances must signal success for the * update to succeed. */ -export interface AutoScalingCreationPolicy { +export interface CfnResourceAutoScalingCreationPolicy { /** * Specifies the percentage of instances in an Auto Scaling replacement update that must signal success for the * update to succeed. You can specify a value from 0 to 100. AWS CloudFormation rounds to the nearest tenth of a percent. @@ -47,7 +47,7 @@ export interface AutoScalingCreationPolicy { * When AWS CloudFormation creates the associated resource, configures the number of required success signals and * the length of time that AWS CloudFormation waits for those signals. */ -export interface ResourceSignal { +export interface CfnResourceSignal { /** * The number of success signals AWS CloudFormation must receive before it sets the resource status as CREATE_COMPLETE. @@ -70,7 +70,7 @@ export interface ResourceSignal { * attribute, AWS CloudFormation deletes the resource by default. Note that this capability also applies to update operations * that lead to resources being removed. */ -export enum DeletionPolicy { +export enum CfnDeletionPolicy { /** * AWS CloudFormation deletes the resource and all its content if applicable during stack deletion. You can add this * deletion policy to any resource type. By default, if you don't specify a DeletionPolicy, AWS CloudFormation deletes @@ -101,34 +101,34 @@ export enum DeletionPolicy { * resource. AWS CloudFormation invokes one of three update policies depending on the type of change you make or whether a * scheduled action is associated with the Auto Scaling group. */ -export interface UpdatePolicy { +export interface CfnUpdatePolicy { /** * Specifies whether an Auto Scaling group and the instances it contains are replaced during an update. During replacement, * AWS CloudFormation retains the old group until it finishes creating the new one. If the update fails, AWS CloudFormation * can roll back to the old Auto Scaling group and delete the new Auto Scaling group. */ - readonly autoScalingReplacingUpdate?: AutoScalingReplacingUpdate; + readonly autoScalingReplacingUpdate?: CfnAutoScalingReplacingUpdate; /** * To specify how AWS CloudFormation handles rolling updates for an Auto Scaling group, use the AutoScalingRollingUpdate * policy. Rolling updates enable you to specify whether AWS CloudFormation updates instances that are in an Auto Scaling * group in batches or all at once. */ - readonly autoScalingRollingUpdate?: AutoScalingRollingUpdate; + readonly autoScalingRollingUpdate?: CfnAutoScalingRollingUpdate; /** * To specify how AWS CloudFormation handles updates for the MinSize, MaxSize, and DesiredCapacity properties when * the AWS::AutoScaling::AutoScalingGroup resource has an associated scheduled action, use the AutoScalingScheduledAction * policy. */ - readonly autoScalingScheduledAction?: AutoScalingScheduledAction; + readonly autoScalingScheduledAction?: CfnAutoScalingScheduledAction; /** * To perform an AWS CodeDeploy deployment when the version changes on an AWS::Lambda::Alias resource, * use the CodeDeployLambdaAliasUpdate update policy. */ - readonly codeDeployLambdaAliasUpdate?: CodeDeployLambdaAliasUpdate; + readonly codeDeployLambdaAliasUpdate?: CfnCodeDeployLambdaAliasUpdate; /** * To modify a replication group's shards by adding or removing shards, rather than replacing the entire @@ -143,7 +143,7 @@ export interface UpdatePolicy { * policy. Rolling updates enable you to specify whether AWS CloudFormation updates instances that are in an Auto Scaling * group in batches or all at once. */ -export interface AutoScalingRollingUpdate { +export interface CfnAutoScalingRollingUpdate { /** * Specifies the maximum number of instances that AWS CloudFormation updates. @@ -219,7 +219,7 @@ export interface AutoScalingRollingUpdate { * (specified in the CreationPolicy policy), the replacement update fails and AWS CloudFormation rolls back to the old * Auto Scaling group. */ -export interface AutoScalingReplacingUpdate { +export interface CfnAutoScalingReplacingUpdate { readonly willReplace?: boolean; } @@ -233,7 +233,7 @@ export interface AutoScalingReplacingUpdate { * effect, use the AutoScalingScheduledAction update policy to prevent AWS CloudFormation from changing the MinSize, MaxSize, * or DesiredCapacity properties unless you have modified these values in your template.\ */ -export interface AutoScalingScheduledAction { +export interface CfnAutoScalingScheduledAction { /* * Specifies whether AWS CloudFormation ignores differences in group size properties between your current Auto Scaling * group and the Auto Scaling group described in the AWS::AutoScaling::AutoScalingGroup resource of your template during @@ -247,7 +247,7 @@ export interface AutoScalingScheduledAction { * To perform an AWS CodeDeploy deployment when the version changes on an AWS::Lambda::Alias resource, * use the CodeDeployLambdaAliasUpdate update policy. */ -export interface CodeDeployLambdaAliasUpdate { +export interface CfnCodeDeployLambdaAliasUpdate { /** * The name of the AWS CodeDeploy application. */ diff --git a/packages/@aws-cdk/cdk/lib/cfn-resource.ts b/packages/@aws-cdk/cdk/lib/cfn-resource.ts index 3fff5bc52d9ad..5acf741ee2142 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-resource.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-resource.ts @@ -3,48 +3,32 @@ import { CfnCondition } from './cfn-condition'; // import required to be here, otherwise causes a cycle when running the generated JavaScript // tslint:disable-next-line:ordered-imports import { CfnRefElement } from './cfn-element'; +import { CfnCreationPolicy, CfnDeletionPolicy, CfnUpdatePolicy } from './cfn-resource-policy'; import { Construct, IConstruct } from './construct'; import { CfnReference } from './private/cfn-reference'; import { RemovalPolicy, RemovalPolicyOptions } from './removal-policy'; import { IResolvable } from './resolvable'; -import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { TagManager } from './tag-manager'; import { capitalizePropertyNames, ignoreEmpty, PostResolveToken } from './util'; export interface CfnResourceProps { /** - * CloudFormation resource type. + * CloudFormation resource type (e.g. `AWS::S3::Bucket`). */ readonly type: string; /** - * CloudFormation properties. + * Resource properties. * * @default - No resource properties. */ - readonly properties?: any; + readonly properties?: { [name: string]: any }; } /** * Represents a CloudFormation resource. */ export class CfnResource extends CfnRefElement { - /** - * A decoration used to create a CloudFormation attribute property. - * @param customName Custom name for the attribute (default is the name of the property) - * NOTE: we return "any" here to satistfy jsii, which doesn't support lambdas. - */ - public static attribute(customName?: string): any { - return (prototype: any, key: string) => { - const name = customName || key; - Object.defineProperty(prototype, key, { - get() { - return (this as any).getAtt(name); - } - }); - }; - } - /** * Check whether the given construct is a CfnResource */ @@ -116,11 +100,11 @@ export class CfnResource extends CfnRefElement { switch (policy) { case RemovalPolicy.Destroy: - deletionPolicy = DeletionPolicy.Delete; + deletionPolicy = CfnDeletionPolicy.Delete; break; case RemovalPolicy.Retain: - deletionPolicy = DeletionPolicy.Retain; + deletionPolicy = CfnDeletionPolicy.Retain; break; default: @@ -231,7 +215,7 @@ export class CfnResource extends CfnRefElement { Type: this.cfnResourceType, Properties: ignoreEmpty(this.cfnProperties), DependsOn: ignoreEmpty(renderDependsOn(this.dependsOn)), - CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), + CreationPolicy: capitalizePropertyNames(this, renderCreationPolicy(this.options.creationPolicy)), UpdatePolicy: capitalizePropertyNames(this, this.options.updatePolicy), UpdateReplacePolicy: capitalizePropertyNames(this, this.options.updateReplacePolicy), DeletionPolicy: capitalizePropertyNames(this, this.options.deletionPolicy), @@ -267,6 +251,15 @@ export class CfnResource extends CfnRefElement { .sort((x, y) => x.node.path.localeCompare(y.node.path)) .map(r => r.logicalId); } + + function renderCreationPolicy(policy: CfnCreationPolicy | undefined): any { + if (!policy) { return undefined; } + const result: any = { ...policy }; + if (policy.resourceSignal && policy.resourceSignal.timeout) { + result.resourceSignal = policy.resourceSignal; + } + return result; + } } protected get cfnProperties(): { [key: string]: any } { @@ -315,7 +308,7 @@ export interface IResourceOptions { * resource, you can use the cfn-signal helper script or SignalResource API. AWS CloudFormation publishes valid signals * to the stack events so that you track the number of signals sent. */ - creationPolicy?: CreationPolicy; + creationPolicy?: CfnCreationPolicy; /** * With the DeletionPolicy attribute you can preserve or (in some cases) backup a resource when its stack is deleted. @@ -323,20 +316,20 @@ export interface IResourceOptions { * attribute, AWS CloudFormation deletes the resource by default. Note that this capability also applies to update operations * that lead to resources being removed. */ - deletionPolicy?: DeletionPolicy; + deletionPolicy?: CfnDeletionPolicy; /** * Use the UpdatePolicy attribute to specify how AWS CloudFormation handles updates to the AWS::AutoScaling::AutoScalingGroup * resource. AWS CloudFormation invokes one of three update policies depending on the type of change you make or whether a * scheduled action is associated with the Auto Scaling group. */ - updatePolicy?: UpdatePolicy; + updatePolicy?: CfnUpdatePolicy; /** * Use the UpdateReplacePolicy attribute to retain or (in some cases) backup the existing physical instance of a resource * when it is replaced during a stack update operation. */ - updateReplacePolicy?: DeletionPolicy; + updateReplacePolicy?: CfnDeletionPolicy; /** * Metadata associated with the CloudFormation resource. This is not the same as the construct metadata which can be added @@ -349,7 +342,7 @@ export interface IResourceOptions { * Merges `source` into `target`, overriding any existing values. * `null`s will cause a value to be deleted. */ -export function deepMerge(target: any, ...sources: any[]) { +function deepMerge(target: any, ...sources: any[]) { for (const source of sources) { if (typeof(source) !== 'object' || typeof(target) !== 'object') { throw new Error(`Invalid usage. Both source (${JSON.stringify(source)}) and target (${JSON.stringify(target)}) must be objects`); diff --git a/packages/@aws-cdk/cdk/lib/cfn-rule.ts b/packages/@aws-cdk/cdk/lib/cfn-rule.ts index bf41309eb0bbc..9ce9417eff7e9 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-rule.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-rule.ts @@ -38,7 +38,7 @@ export interface CfnRuleProps { * * @default - No assertions for the rule. */ - readonly assertions?: RuleAssertion[]; + readonly assertions?: CfnRuleAssertion[]; } /** @@ -57,16 +57,8 @@ export interface CfnRuleProps { * @link https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html */ export class CfnRule extends CfnRefElement { - /** - * If the rule condition evaluates to false, the rule doesn't take effect. - * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. - */ - public ruleCondition?: ICfnConditionExpression; - - /** - * Assertions which define the rule. - */ - public assertions?: RuleAssertion[]; + private ruleCondition?: ICfnConditionExpression; + private assertions?: CfnRuleAssertion[]; /** * Creates and adds a rule. @@ -114,7 +106,7 @@ export class CfnRule extends CfnRefElement { /** * A rule assertion. */ -export interface RuleAssertion { +export interface CfnRuleAssertion { /** * The assertion. */ diff --git a/packages/@aws-cdk/cdk/lib/tag.ts b/packages/@aws-cdk/cdk/lib/cfn-tag.ts similarity index 100% rename from packages/@aws-cdk/cdk/lib/tag.ts rename to packages/@aws-cdk/cdk/lib/cfn-tag.ts diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 3a94d1eabe3d3..68c744c357966 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -1,10 +1,10 @@ import cxapi = require('@aws-cdk/cx-api'); import { IAspect } from './aspect'; import { DependableTrait, IDependable } from './dependency'; +import { makeUniqueId } from './private/uniqueid'; import { IResolvable } from './resolvable'; import { captureStackTrace } from './stack-trace'; import { Token } from './token'; -import { makeUniqueId } from './uniqueid'; const CONSTRUCT_SYMBOL = Symbol.for('@aws-cdk/cdk.Construct'); @@ -13,7 +13,7 @@ const CONSTRUCT_SYMBOL = Symbol.for('@aws-cdk/cdk.Construct'); */ export interface IConstruct extends IDependable { /** - * The construct node in the scope tree. + * The construct node in the tree. */ readonly node: ConstructNode; } @@ -198,7 +198,7 @@ export class ConstructNode { /** * Return a descendant by path * - * Throws an exception if the descendant is not found. + * Throws an error if the descendant is not found. * * Note that if the original ID of the construct you are looking for contained * a '/', then it would have been replaced by '--'. @@ -215,7 +215,7 @@ export class ConstructNode { } /** - * Returns the child construct that has the id "Default" or "Resource". + * Returns the child construct that has the id `Default` or `Resource"` * @throws if there is more than one child * @returns a construct or undefined if there is no default child */ diff --git a/packages/@aws-cdk/cdk/lib/dependency.ts b/packages/@aws-cdk/cdk/lib/dependency.ts index a62ce86f49285..e64f0692ce7e4 100644 --- a/packages/@aws-cdk/cdk/lib/dependency.ts +++ b/packages/@aws-cdk/cdk/lib/dependency.ts @@ -39,6 +39,8 @@ export class ConcreteDependable implements IDependable { } } +const DEPENDABLE_SYMBOL = Symbol.for('@aws-cdk/core.DependableTrait'); + /** * Trait for IDependable * @@ -68,22 +70,20 @@ export abstract class DependableTrait { // I would also like to reference classes (to cut down on the list of objects // we need to manage), but we can't do that either since jsii doesn't have the // concept of a class reference. - DependableTrait.traitMap.set(instance, trait); + (instance as any)[DEPENDABLE_SYMBOL] = trait; } /** * Return the matching DependableTrait for the given class instance. */ public static get(instance: IDependable): DependableTrait { - const ret = DependableTrait.traitMap.get(instance); + const ret = (instance as any)[DEPENDABLE_SYMBOL]; if (!ret) { throw new Error(`${instance} does not implement DependableTrait`); } return ret; } - private static traitMap = new WeakMap(); - /** * The set of constructs that form the root of this dependable * @@ -91,4 +91,4 @@ export abstract class DependableTrait { * dependency. */ public abstract readonly dependencyRoots: IConstruct[]; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/lib/duration.ts b/packages/@aws-cdk/cdk/lib/duration.ts new file mode 100644 index 0000000000000..4640fa33b399c --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/duration.ts @@ -0,0 +1,196 @@ +import { Token } from "./token"; + +/** + * Represents a length of time. + * + * The amount can be specified either as a literal value (e.g: `10`) which + * cannot be negative, or as an unresolved number token. + * + * Whent he amount is passed as an token, unit conversion is not possible. + */ +export class Duration { + /** + * @param amount the amount of Seconds the `Duration` will represent. + * @returns a new `Duration` representing `amount` Seconds. + */ + public static seconds(amount: number): Duration { + return new Duration(amount, TimeUnit.Seconds); + } + + /** + * @param amount the amount of Minutes the `Duration` will represent. + * @returns a new `Duration` representing `amount` Minutes. + */ + public static minutes(amount: number): Duration { + return new Duration(amount, TimeUnit.Minutes); + } + + /** + * @param amount the amount of Hours the `Duration` will represent. + * @returns a new `Duration` representing `amount` Hours. + */ + public static hours(amount: number): Duration { + return new Duration(amount, TimeUnit.Hours); + } + + /** + * @param amount the amount of Days the `Duration` will represent. + * @returns a new `Duration` representing `amount` Days. + */ + public static days(amount: number): Duration { + return new Duration(amount, TimeUnit.Days); + } + + /** + * Parse a period formatted according to the ISO 8601 standard (see https://www.iso.org/fr/standard/70907.html). + * + * @param duration an ISO-formtted duration to be parsed. + * @returns the parsed `Duration`. + */ + public static parse(duration: string): Duration { + const matches = duration.match(/^PT(?:(\d+)D)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/); + if (!matches) { + throw new Error(`Not a valid ISO duration: ${duration}`); + } + const [, days, hours, minutes, seconds] = matches; + if (!days && !hours && !minutes && !seconds) { + throw new Error(`Not a valid ISO duration: ${duration}`); + } + return Duration.seconds( + _toInt(seconds) + + (_toInt(minutes) * TimeUnit.Minutes.inSeconds) + + (_toInt(hours) * TimeUnit.Hours.inSeconds) + + (_toInt(days) * TimeUnit.Days.inSeconds) + ); + + function _toInt(str: string): number { + if (!str) { return 0; } + return Number(str); + } + } + + private readonly amount: number; + private readonly unit: TimeUnit; + + private constructor(amount: number, unit: TimeUnit) { + if (!Token.isUnresolved(amount) && amount < 0) { + throw new Error(`Duration amounts cannot be negative. Received: ${amount}`); + } + + this.amount = amount; + this.unit = unit; + } + + /** + * @returns the value of this `Duration` expressed in Seconds. + */ + public toSeconds(opts: TimeConversionOptions = {}): number { + return convert(this.amount, this.unit, TimeUnit.Seconds, opts); + } + + /** + * @returns the value of this `Duration` expressed in Minutes. + */ + public toMinutes(opts: TimeConversionOptions = {}): number { + return convert(this.amount, this.unit, TimeUnit.Minutes, opts); + } + + /** + * @returns the value of this `Duration` expressed in Hours. + */ + public toHours(opts: TimeConversionOptions = {}): number { + return convert(this.amount, this.unit, TimeUnit.Hours, opts); + } + + /** + * @returns the value of this `Duration` expressed in Days. + */ + public toDays(opts: TimeConversionOptions = {}): number { + return convert(this.amount, this.unit, TimeUnit.Days, opts); + } + + /** + * @returns an ISO 8601 representation of this period (see https://www.iso.org/fr/standard/70907.html). + */ + public toISOString(): string { + if (this.amount === 0) { return 'PT0S'; } + switch (this.unit) { + case TimeUnit.Seconds: + return `PT${this.fractionDuration('S', 60, Duration.minutes)}`; + case TimeUnit.Minutes: + return `PT${this.fractionDuration('M', 60, Duration.hours)}`; + case TimeUnit.Hours: + return `PT${this.fractionDuration('H', 24, Duration.days)}`; + case TimeUnit.Days: + return `PT${this.amount}D`; + default: + throw new Error(`Unexpected time unit: ${this.unit}`); + } + } + + /** + * Returns a string representation of this `Duration` that is also a Token that cannot be successfully resolved. This + * protects users against inadvertently stringifying a `Duration` object, when they should have called one of the + * `to*` methods instead. + */ + public toString(): string { + return Token.asString( + () => { + throw new Error(`Duration.toString() was used, but .toSeconds, .toMinutes or .toDays should have been called instead`); + }, + { displayHint: `${this.amount} ${this.unit.label}` } + ); + } + + private fractionDuration(symbol: string, modulus: number, next: (amount: number) => Duration): string { + if (this.amount < modulus) { + return `${this.amount}${symbol}`; + } + const remainder = this.amount % modulus; + const quotient = next((this.amount - remainder) / modulus).toISOString().slice(2); + return remainder > 0 + ? `${quotient}${remainder}${symbol}` + : quotient; + } +} + +/** + * Options for how to convert time to a different unit. + */ +export interface TimeConversionOptions { + /** + * If `true`, conversions into a larger time unit (e.g. `Seconds` to `Mintues`) will fail if the result is not an + * integer. + * + * @default true + */ + readonly integral?: boolean; +} + +class TimeUnit { + public static readonly Seconds = new TimeUnit('seconds', 1); + public static readonly Minutes = new TimeUnit('minutes', 60); + public static readonly Hours = new TimeUnit('hours', 3_600); + public static readonly Days = new TimeUnit('days', 86_400); + + private constructor(public readonly label: string, public readonly inSeconds: number) { + } + + public toString() { + return this.label; + } +} + +function convert(amount: number, fromUnit: TimeUnit, toUnit: TimeUnit, { integral = true }: TimeConversionOptions) { + if (fromUnit.inSeconds === toUnit.inSeconds) { return amount; } + const multiplier = fromUnit.inSeconds / toUnit.inSeconds; + + if (Token.isUnresolved(amount)) { + throw new Error(`Unable to perform time unit conversion on un-resolved token ${amount}.`); + } + const value = amount * multiplier; + if (!Number.isInteger(value) && integral) { + throw new Error(`'${amount} ${fromUnit}' cannot be converted into a whole number of ${toUnit}.`); + } + return value; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index a624d4cdf9892..604fde23ad45a 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -11,21 +11,22 @@ export * from './string-fragments'; export * from './reference'; export * from './cfn-condition'; -export * from './fn'; -export * from './include'; +export * from './cfn-fn'; +export * from './cfn-include'; export * from './cfn-mapping'; export * from './cfn-output'; export * from './cfn-parameter'; -export * from './pseudo'; +export * from './cfn-pseudo'; export * from './cfn-resource'; -export * from './resource-policy'; +export * from './cfn-resource-policy'; export * from './cfn-rule'; export * from './stack'; export * from './cfn-element'; export * from './cfn-dynamic-reference'; -export * from './tag'; +export * from './cfn-tag'; export * from './removal-policy'; export * from './arn'; +export * from './duration'; export * from './stack-trace'; export * from './app'; @@ -37,7 +38,6 @@ export * from './secret-value'; export * from './resource'; export * from './physical-name'; -export * from './resource-identifiers'; // WARNING: Should not be exported, but currently is because of a bug. See the // class description for more information. diff --git a/packages/@aws-cdk/cdk/lib/lazy.ts b/packages/@aws-cdk/cdk/lib/lazy.ts index 6c6d8ee35d99f..9c7d3d0faa0f9 100644 --- a/packages/@aws-cdk/cdk/lib/lazy.ts +++ b/packages/@aws-cdk/cdk/lib/lazy.ts @@ -5,7 +5,7 @@ import { Token } from "./token"; /** * Interface for lazy string producers */ -export interface IStringValue { +export interface IStringProducer { /** * Produce the string value */ @@ -99,7 +99,7 @@ export interface LazyAnyValueOptions { * will only be calculated later, during synthesis. */ export class Lazy { - public static stringValue(producer: IStringValue, options: LazyStringValueOptions = {}) { + public static stringValue(producer: IStringProducer, options: LazyStringValueOptions = {}) { return Token.asString(new LazyString(producer), options); } @@ -143,7 +143,7 @@ abstract class LazyBase implements IResolvable { } class LazyString extends LazyBase { - constructor(private readonly producer: IStringValue) { + constructor(private readonly producer: IStringProducer) { super(); } diff --git a/packages/@aws-cdk/cdk/lib/physical-name.ts b/packages/@aws-cdk/cdk/lib/physical-name.ts index b1a8b0d88fe98..cf814901292ab 100644 --- a/packages/@aws-cdk/cdk/lib/physical-name.ts +++ b/packages/@aws-cdk/cdk/lib/physical-name.ts @@ -1,5 +1,5 @@ import { Lazy } from "./lazy"; -import { generatePhysicalName } from "./physical-name-generator"; +import { generatePhysicalName } from "./private/physical-name-generator"; import { IResource } from './resource'; /** @@ -62,7 +62,7 @@ export abstract class PhysicalName { * can be `undefined`, if a physical name was not provided, * or an opaque Token, if a generated name should be assigned lazily at synthesis time. */ - public abstract get value(): string | undefined; + public abstract get value(): string; /** * A callback method called if the resource this physical name @@ -96,20 +96,18 @@ class LateBoundPhysicalName extends PhysicalName { * either a Token string if crossEnvironment is true, * or undefined otherwise. */ - public readonly value: string | undefined; + public readonly value: string; + private name?: string; - constructor(options: AutoPhysicalNameOptions) { + constructor(private readonly options: AutoPhysicalNameOptions) { super(); - - this.value = options.crossEnvironment - ? Lazy.stringValue({ produce: () => this.name }) - : undefined; + this.value = Lazy.stringValue({ produce: () => this.name }); } /** @internal */ public _resolveCrossEnvironment(resource: IResource): void { - if (!this.value) { + if (!this.options.crossEnvironment) { // error out - a deploy-time name cannot be used across environments throw new Error(`Cannot use resource '${resource.node.path}' in a cross-environment fashion, ` + "as it doesn't have a physical name set. Use PhysicalName.auto({ crossEnvironment: true }) to enable cross env name allocation"); diff --git a/packages/@aws-cdk/cdk/lib/private/cfn-reference.ts b/packages/@aws-cdk/cdk/lib/private/cfn-reference.ts index 538d8cf987e43..24aca74187b74 100644 --- a/packages/@aws-cdk/cdk/lib/private/cfn-reference.ts +++ b/packages/@aws-cdk/cdk/lib/private/cfn-reference.ts @@ -1,4 +1,5 @@ import { Reference } from "../reference"; +import { makeUniqueId } from './uniqueid'; const CFN_REFERENCE_SYMBOL = Symbol.for('@aws-cdk/cdk.CfnReference'); @@ -122,7 +123,7 @@ export class CfnReference extends Reference { if (this.producingStack && this.producingStack !== consumingStack && !this.replacementTokens.has(consumingStack)) { // We're trying to resolve a cross-stack reference consumingStack.addDependency(this.producingStack, `${consumingConstruct.node.path} -> ${this.target.node.path}.${this.originalDisplayName}`); - this.replacementTokens.set(consumingStack, this.exportValue(this, consumingStack)); + this.replacementTokens.set(consumingStack, this.exportValue(consumingStack)); } } @@ -140,7 +141,7 @@ export class CfnReference extends Reference { * * Works by mutating the producing stack in-place. */ - private exportValue(tokenValue: Token, consumingStack: Stack): IResolvable { + private exportValue(consumingStack: Stack): IResolvable { const producingStack = this.producingStack!; if (producingStack.environment !== consumingStack.environment) { @@ -159,16 +160,25 @@ export class CfnReference extends Reference { } // Ensure a singleton CfnOutput for this value - const resolved = producingStack.resolve(tokenValue); + const resolved = producingStack.resolve(this); const id = 'Output' + JSON.stringify(resolved); + const exportName = this.generateExportName(stackExports, id); let output = stackExports.node.tryFindChild(id) as CfnOutput; if (!output) { - output = new CfnOutput(stackExports, id, { value: tokenValue }); + output = new CfnOutput(stackExports, id, { value: Token.asString(this), exportName }); } // We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string', // so construct one in-place. - return new Intrinsic({ 'Fn::ImportValue': output.obtainExportName() }); + return new Intrinsic({ 'Fn::ImportValue': exportName }); + } + + private generateExportName(stackExports: Construct, id: string) { + const stack = Stack.of(stackExports); + const components = [...stackExports.node.scopes.slice(2).map(c => c.node.id), id]; + const prefix = stack.stackName ? stack.stackName + ':' : ''; + const exportName = prefix + makeUniqueId(components); + return exportName; } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation-lang.ts b/packages/@aws-cdk/cdk/lib/private/cloudformation-lang.ts similarity index 95% rename from packages/@aws-cdk/cdk/lib/cloudformation-lang.ts rename to packages/@aws-cdk/cdk/lib/private/cloudformation-lang.ts index b373959aed6d0..b290f4c3b0142 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation-lang.ts +++ b/packages/@aws-cdk/cdk/lib/private/cloudformation-lang.ts @@ -1,9 +1,9 @@ -import { Lazy } from "./lazy"; -import { Intrinsic } from "./private/intrinsic"; -import { resolve } from "./private/resolve"; -import { DefaultTokenResolver, IFragmentConcatenator, IPostProcessor, IResolvable, IResolveContext } from "./resolvable"; -import { TokenizedStringFragments } from "./string-fragments"; -import { Token } from "./token"; +import { Lazy } from "../lazy"; +import { DefaultTokenResolver, IFragmentConcatenator, IPostProcessor, IResolvable, IResolveContext } from "../resolvable"; +import { TokenizedStringFragments } from "../string-fragments"; +import { Token } from "../token"; +import { Intrinsic } from "./intrinsic"; +import { resolve } from "./resolve"; /** * Routines that know how to do operations at the CloudFormation document language level diff --git a/packages/@aws-cdk/cdk/lib/private/cross-environment-token.ts b/packages/@aws-cdk/cdk/lib/private/cross-environment-token.ts index 9c64511f43e72..a98000c440725 100644 --- a/packages/@aws-cdk/cdk/lib/private/cross-environment-token.ts +++ b/packages/@aws-cdk/cdk/lib/private/cross-environment-token.ts @@ -1,6 +1,5 @@ -import { ArnComponents } from '../arn'; import { IResolvable, IResolveContext } from '../resolvable'; -import { IResource } from '../resource'; +import { Resource } from '../resource'; import { Stack } from '../stack'; import { captureStackTrace } from '../stack-trace'; @@ -8,10 +7,9 @@ import { captureStackTrace } from '../stack-trace'; * A Token that represents a reference that spans accounts and/or regions, * and so requires the resources to have physical names. * You should never need to interact with these directly, - * instead use the {@link ResourceIdentifiers} class. - * This class is private to the @aws-cdk/cdk package. + * instead use the `resource.crossEnvironmentTokens` method. */ -export abstract class CrossEnvironmentToken implements IResolvable { +export class CrossEnvironmentAttribute implements IResolvable { public readonly creationStack: string[]; /** @@ -20,9 +18,10 @@ export abstract class CrossEnvironmentToken implements IResolvable { * @param resource the scope this reference is mastered in. Used to determine the owning Stack * @param displayName a short name to be used in Token display */ - protected constructor(private readonly regularValue: string, private readonly crossEnvironmentValue: any, - private readonly resource: IResource) { - this.resource = resource; + constructor( + private readonly regularValue: string, + private readonly crossEnvironmentValue: string, + private readonly resource: Resource) { this.creationStack = captureStackTrace(); } @@ -31,22 +30,10 @@ export abstract class CrossEnvironmentToken implements IResolvable { const owningStack = Stack.of(this.resource); if (consumingStack.environment !== owningStack.environment) { - this.resource.physicalName._resolveCrossEnvironment(this.resource); + this.resource._enableCrossEnvironment(); return this.crossEnvironmentValue; } else { return this.regularValue; } } } - -export class CrossEnvironmentPhysicalArnToken extends CrossEnvironmentToken { - constructor(regularValue: string, arnComponents: ArnComponents, resource: IResource) { - super(regularValue, Stack.of(resource).formatArn(arnComponents), resource); - } -} - -export class CrossEnvironmentPhysicalNameToken extends CrossEnvironmentToken { - constructor(regularValue: string, resource: IResource) { - super(regularValue, resource.physicalName.value, resource); - } -} diff --git a/packages/@aws-cdk/cdk/lib/logical-id.ts b/packages/@aws-cdk/cdk/lib/private/logical-id.ts similarity index 100% rename from packages/@aws-cdk/cdk/lib/logical-id.ts rename to packages/@aws-cdk/cdk/lib/private/logical-id.ts diff --git a/packages/@aws-cdk/cdk/lib/node-version.ts b/packages/@aws-cdk/cdk/lib/private/node-version.ts similarity index 100% rename from packages/@aws-cdk/cdk/lib/node-version.ts rename to packages/@aws-cdk/cdk/lib/private/node-version.ts diff --git a/packages/@aws-cdk/cdk/lib/physical-name-generator.ts b/packages/@aws-cdk/cdk/lib/private/physical-name-generator.ts similarity index 93% rename from packages/@aws-cdk/cdk/lib/physical-name-generator.ts rename to packages/@aws-cdk/cdk/lib/private/physical-name-generator.ts index c57e9abc6a8d4..21aa333ff7f36 100644 --- a/packages/@aws-cdk/cdk/lib/physical-name-generator.ts +++ b/packages/@aws-cdk/cdk/lib/private/physical-name-generator.ts @@ -1,7 +1,7 @@ import crypto = require('crypto'); -import { IResource } from './resource'; -import { Stack } from './stack'; -import { Token } from './token'; +import { IResource } from '../resource'; +import { Stack } from '../stack'; +import { Token } from '../token'; export function generatePhysicalName(resource: IResource): string { const stack = Stack.of(resource); diff --git a/packages/@aws-cdk/cdk/lib/runtime-info.ts b/packages/@aws-cdk/cdk/lib/private/runtime-info.ts similarity index 100% rename from packages/@aws-cdk/cdk/lib/runtime-info.ts rename to packages/@aws-cdk/cdk/lib/private/runtime-info.ts diff --git a/packages/@aws-cdk/cdk/lib/uniqueid.ts b/packages/@aws-cdk/cdk/lib/private/uniqueid.ts similarity index 96% rename from packages/@aws-cdk/cdk/lib/uniqueid.ts rename to packages/@aws-cdk/cdk/lib/private/uniqueid.ts index d7d3f7a48b027..d27ff454beaf4 100644 --- a/packages/@aws-cdk/cdk/lib/uniqueid.ts +++ b/packages/@aws-cdk/cdk/lib/private/uniqueid.ts @@ -1,5 +1,6 @@ // tslint:disable-next-line:no-var-requires import crypto = require('crypto'); +import { unresolved } from './encoding'; /** * Resources with this ID are hidden from humans @@ -36,7 +37,7 @@ export function makeUniqueId(components: string[]) { } // Lazy require in order to break a module dependency cycle - const unresolvedTokens = components.filter(c => require('./private/encoding').unresolved(c)); + const unresolvedTokens = components.filter(c => unresolved(c)); if (unresolvedTokens.length > 0) { throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`); } diff --git a/packages/@aws-cdk/cdk/lib/removal-policy.ts b/packages/@aws-cdk/cdk/lib/removal-policy.ts index 5e32c42749207..eae0e6d2a938e 100644 --- a/packages/@aws-cdk/cdk/lib/removal-policy.ts +++ b/packages/@aws-cdk/cdk/lib/removal-policy.ts @@ -22,6 +22,7 @@ export interface RemovalPolicyOptions { /** * Apply the same deletion policy to the resource's "UpdateReplacePolicy" + * @default false */ readonly applyToUpdateReplacePolicy?: boolean; } diff --git a/packages/@aws-cdk/cdk/lib/resource-identifiers.ts b/packages/@aws-cdk/cdk/lib/resource-identifiers.ts deleted file mode 100644 index 022131e762da6..0000000000000 --- a/packages/@aws-cdk/cdk/lib/resource-identifiers.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ArnComponents } from './arn'; -import { CrossEnvironmentPhysicalArnToken, CrossEnvironmentPhysicalNameToken } from './private/cross-environment-token'; -import { IResource } from './resource'; -import { Token } from './token'; - -/** - * Construction properties for {@link ResourceIdentifiers}. - */ -export interface ResourceIdentifiersProps { - /** - * The ARN of the resource when referenced from the same stack. - */ - readonly arn: string; - - /** - * The name of the resource when referenced from the same stack. - */ - readonly name: string; - - /** - * The recipe for creating an ARN from a name for this resource. - */ - readonly arnComponents: ArnComponents; -} - -/** - * The identifiers (name and ARN) for a given L2. - * These should be only used inside the Construct Library implementation. - */ -export class ResourceIdentifiers { - public readonly arn: string; - public readonly name: string; - - constructor(resource: IResource, props: ResourceIdentifiersProps) { - this.arn = Token.asString(new CrossEnvironmentPhysicalArnToken( - props.arn, - props.arnComponents, - resource, - ), { displayHint: 'Arn' }); - - this.name = Token.asString(new CrossEnvironmentPhysicalNameToken( - props.name, - resource, - ), { displayHint: 'Name' }); - } -} diff --git a/packages/@aws-cdk/cdk/lib/resource.ts b/packages/@aws-cdk/cdk/lib/resource.ts index 32d69f9a8b211..6920d9455eee7 100644 --- a/packages/@aws-cdk/cdk/lib/resource.ts +++ b/packages/@aws-cdk/cdk/lib/resource.ts @@ -1,14 +1,18 @@ +import { ArnComponents } from './arn'; import { Construct, IConstruct } from './construct'; import { PhysicalName } from './physical-name'; +import { CrossEnvironmentAttribute as CrossEnvironmentAttribute } from './private/cross-environment-token'; +import { Stack } from './stack'; +import { Token } from './token'; /** * Interface for the Resource construct. */ export interface IResource extends IConstruct { /** - * The physical (that is, visible in the AWS Console) name of this resource. + * The stack in which this resource is defined. */ - readonly physicalName: PhysicalName; + readonly stack: Stack; } /** @@ -17,8 +21,8 @@ export interface IResource extends IConstruct { export interface ResourceProps { /** * The physical (that is, visible in the AWS Console) name of this resource. - * By default, the name will be automatically generated by CloudFormation, - * at deploy time. + * By default, the name will be automatically generated by CloudFormation, at + * deploy time. * * @default PhysicalName.auto() */ @@ -29,11 +33,84 @@ export interface ResourceProps { * A construct which represents an AWS resource. */ export abstract class Resource extends Construct implements IResource { - public readonly physicalName: PhysicalName; + public readonly stack: Stack; + + private readonly _physicalName: PhysicalName; constructor(scope: Construct, id: string, props: ResourceProps = {}) { super(scope, id); + this.stack = Stack.of(this); + this._physicalName = props.physicalName || PhysicalName.auto(); + } + + /** + * Called when this resource is referenced across environments (account/region) to order to + * request that a physical name will be generated for this resource during synthesis, so the + * resource can be referenced through it's absolute name/arn. + * + * @internal + */ + public _enableCrossEnvironment(): void { + this._physicalName._resolveCrossEnvironment(this); + } - this.physicalName = props.physicalName || PhysicalName.auto(); + /** + * Returns a string-encoded token that resolves to the physical name that should be + * passed to the CloudFormation resource. + * + * This token will resolve to one of: + * - `undefined`, when a name should be generated by cloudformation + * - a concrete value, when the user supplied a name through `PhysicalName.of(x)` + * - a name generated automatically during synthesis, in cross-account scenario. + * + * @experimental + */ + protected get physicalName(): string { + return this._physicalName.value; + } + + /** + * @experimental + */ + protected getCrossEnvironmentAttributes(recipe: CrossEnvironmentRecipe): CrossEnvironmentAttributes { + const arnToken = new CrossEnvironmentAttribute(recipe.arn, this.stack.formatArn(recipe.arnComponents), this); + const nameToken = new CrossEnvironmentAttribute(recipe.name, this.physicalName, this); + return { + arn: Token.asString(arnToken, { displayHint: 'Arn' }), + name: Token.asString(nameToken, { displayHint: 'Name' }) + }; } } + +/** + * Construction properties for {@link ResourceIdentifiers}. + * + * @experimental + */ +export interface CrossEnvironmentRecipe { + /** + * The ARN of the resource when referenced from the same stack. + */ + readonly arn: string; + + /** + * The name of the resource when referenced from the same stack. + */ + readonly name: string; + + /** + * The recipe for creating an ARN from a name for this resource. + */ + readonly arnComponents: ArnComponents; +} + +/** + * The identifiers (name and ARN) for a given L2. + * These should be only used inside the Construct Library implementation. + * + * @experimental + */ +export interface CrossEnvironmentAttributes { + readonly arn: string; + readonly name: string; +} diff --git a/packages/@aws-cdk/cdk/lib/runtime.ts b/packages/@aws-cdk/cdk/lib/runtime.ts index 78694558a6755..ff8d3673a0e44 100644 --- a/packages/@aws-cdk/cdk/lib/runtime.ts +++ b/packages/@aws-cdk/cdk/lib/runtime.ts @@ -57,7 +57,7 @@ export function cfnTagToCloudFormation(x: any): any { export function listMapper(elementMapper: Mapper): Mapper { return (x: any) => { - if (!canInspect(x)) { return x; } + if (!canInspect(x)) { return x; } return x.map(elementMapper); }; } @@ -250,7 +250,7 @@ export function validateObject(x: any): ValidationResult { } export function validateCfnTag(x: any): ValidationResult { - if (!canInspect(x)) { return VALIDATION_SUCCESS; } + if (!canInspect(x)) { return VALIDATION_SUCCESS; } if (x.key == null || x.value == null) { return new ValidationResult(`${JSON.stringify(x)} should have a 'key' and a 'value' property`); @@ -264,7 +264,7 @@ export function validateCfnTag(x: any): ValidationResult { */ export function listValidator(elementValidator: Validator): Validator { return (x: any) => { - if (!canInspect(x)) { return VALIDATION_SUCCESS; } + if (!canInspect(x)) { return VALIDATION_SUCCESS; } if (!x.forEach) { return new ValidationResult(`${JSON.stringify(x)} should be a list`); diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 3672ddb3b0a4d..bb48ff67d36f0 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -2,13 +2,13 @@ import cxapi = require('@aws-cdk/cx-api'); import { EnvironmentUtils } from '@aws-cdk/cx-api'; import fs = require('fs'); import path = require('path'); -import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './cloudformation-lang'; import { Construct, ConstructNode, IConstruct, ISynthesisSession } from './construct'; import { ContextProvider } from './context-provider'; import { Environment } from './environment'; -import { LogicalIDs } from './logical-id'; +import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './private/cloudformation-lang'; +import { LogicalIDs } from './private/logical-id'; import { resolve } from './private/resolve'; -import { makeUniqueId } from './uniqueid'; +import { makeUniqueId } from './private/uniqueid'; const STACK_SYMBOL = Symbol.for('@aws-cdk/cdk.Stack'); const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/; @@ -335,43 +335,6 @@ export class Stack extends Construct implements ITaggable { return Arn.format(components, this); } - /** - * Returnst the list of AZs that are availability in the AWS environment - * (account/region) associated with this stack. - * - * If the stack is environment-agnostic (either account and/or region are - * tokens), this property will return an array with 2 tokens that will resolve - * at deploy-time to the first two availability zones returned from CloudFormation's - * `Fn::GetAZs` intrinsic function. - * - * If they are not available in the context, returns a set of dummy values and - * reports them as missing, and let the CLI resolve them by calling EC2 - * `DescribeAvailabilityZones` on the target environment. - */ - public get availabilityZones() { - // if account/region are tokens, we can't obtain AZs through the context - // provider, so we fallback to use Fn::GetAZs. the current lowest common - // denominator is 2 AZs across all AWS regions. - const agnostic = Token.isUnresolved(this.account) || Token.isUnresolved(this.region); - if (agnostic) { - return this.node.tryGetContext(cxapi.AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY) || [ - Fn.select(0, Fn.getAZs()), - Fn.select(1, Fn.getAZs()) - ]; - } - - const value = ContextProvider.getValue(this, { - provider: cxapi.AVAILABILITY_ZONE_PROVIDER, - dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], - }); - - if (!Array.isArray(value)) { - throw new Error(`Provider ${cxapi.AVAILABILITY_ZONE_PROVIDER} expects a list`); - } - - return value; - } - /** * Given an ARN, parses it and returns components. * @@ -414,6 +377,43 @@ export class Stack extends Construct implements ITaggable { return Arn.parse(arn, sepIfToken, hasName); } + /** + * Returnst the list of AZs that are availability in the AWS environment + * (account/region) associated with this stack. + * + * If the stack is environment-agnostic (either account and/or region are + * tokens), this property will return an array with 2 tokens that will resolve + * at deploy-time to the first two availability zones returned from CloudFormation's + * `Fn::GetAZs` intrinsic function. + * + * If they are not available in the context, returns a set of dummy values and + * reports them as missing, and let the CLI resolve them by calling EC2 + * `DescribeAvailabilityZones` on the target environment. + */ + public get availabilityZones() { + // if account/region are tokens, we can't obtain AZs through the context + // provider, so we fallback to use Fn::GetAZs. the current lowest common + // denominator is 2 AZs across all AWS regions. + const agnostic = Token.isUnresolved(this.account) || Token.isUnresolved(this.region); + if (agnostic) { + return this.node.tryGetContext(cxapi.AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY) || [ + Fn.select(0, Fn.getAZs()), + Fn.select(1, Fn.getAZs()) + ]; + } + + const value = ContextProvider.getValue(this, { + provider: cxapi.AVAILABILITY_ZONE_PROVIDER, + dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'], + }); + + if (!Array.isArray(value)) { + throw new Error(`Provider ${cxapi.AVAILABILITY_ZONE_PROVIDER} expects a list`); + } + + return value; + } + /** * Returns the naming scheme used to allocate logical IDs. By default, uses * the `HashedAddressingScheme` but this method can be overridden to customize @@ -724,10 +724,10 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { // These imports have to be at the end to prevent circular imports import { Arn, ArnComponents } from './arn'; import { CfnElement } from './cfn-element'; +import { Fn } from './cfn-fn'; +import { Aws, ScopedAws } from './cfn-pseudo'; import { CfnResource, TagType } from './cfn-resource'; -import { Fn } from './fn'; import { CfnReference } from './private/cfn-reference'; -import { Aws, ScopedAws } from './pseudo'; import { ITaggable, TagManager } from './tag-manager'; import { Token } from './token'; diff --git a/packages/@aws-cdk/cdk/lib/tag-aspect.ts b/packages/@aws-cdk/cdk/lib/tag-aspect.ts index d2f50ca7cd1f5..b21a8806c571e 100644 --- a/packages/@aws-cdk/cdk/lib/tag-aspect.ts +++ b/packages/@aws-cdk/cdk/lib/tag-aspect.ts @@ -57,7 +57,7 @@ export interface TagProps { /** * The common functionality for Tag and Remove Tag Aspects */ -export abstract class TagBase implements IAspect { +abstract class TagBase implements IAspect { /** * The string key for the tag diff --git a/packages/@aws-cdk/cdk/lib/tag-manager.ts b/packages/@aws-cdk/cdk/lib/tag-manager.ts index ed6a2e6504136..bfbe339aca96f 100644 --- a/packages/@aws-cdk/cdk/lib/tag-manager.ts +++ b/packages/@aws-cdk/cdk/lib/tag-manager.ts @@ -1,5 +1,5 @@ import { TagType } from './cfn-resource'; -import { CfnTag } from './tag'; +import { CfnTag } from './cfn-tag'; interface Tag { key: string; diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 4e6c5441d1fcc..e56015976b7fa 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/package.json b/packages/@aws-cdk/cdk/package.json index afd66bd76b80b..1e59cacd00909 100644 --- a/packages/@aws-cdk/cdk/package.json +++ b/packages/@aws-cdk/cdk/package.json @@ -34,13 +34,11 @@ "awslint": { "exclude": [ "construct-ctor:@aws-cdk/cdk.App.", - "construct-ctor:@aws-cdk/cdk.Root.", - "construct-ctor:@aws-cdk/cdk.Stack..params*", - "props-no-any:@aws-cdk/cdk.CfnOutputProps.value", + "props-no-cfn-types:@aws-cdk/cdk.CfnOutputProps.condition", + "duration-prop-type:@aws-cdk/cdk.ResourceSignal.timeout", "props-no-any:@aws-cdk/cdk.CfnParameterProps.default", - "props-no-any:@aws-cdk/cdk.CfnResourceProps.properties", - "props-no-cfn-types:@aws-cdk/cdk.CfnOutputProps*", - "props-no-cfn-types:@aws-cdk/cdk.StringListCfnOutputProps*" + "props-no-cfn-types:@aws-cdk/cdk.CfnRuleProps.assertions", + "construct-ctor:@aws-cdk/cdk.Stack..params[1]" ] }, "scripts": { @@ -90,4 +88,4 @@ "node": ">= 8.10.0" }, "stability": "experimental" -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/test/evaluate-cfn.ts b/packages/@aws-cdk/cdk/test/evaluate-cfn.ts index 0ae333cef60bb..1ce93ae985180 100644 --- a/packages/@aws-cdk/cdk/test/evaluate-cfn.ts +++ b/packages/@aws-cdk/cdk/test/evaluate-cfn.ts @@ -3,7 +3,7 @@ * * Note that this function is not production quality, it exists to support tests. */ -import { isNameOfCloudFormationIntrinsic } from '../lib/cloudformation-lang'; +import { isNameOfCloudFormationIntrinsic } from '../lib/private/cloudformation-lang'; export function evaluateCFN(object: any, context: {[key: string]: string} = {}): any { const intrinsics: any = { diff --git a/packages/@aws-cdk/cdk/test/test.cross-environment-token.ts b/packages/@aws-cdk/cdk/test/test.cross-environment-token.ts index b8e49004ba9be..2a30d6883052a 100644 --- a/packages/@aws-cdk/cdk/test/test.cross-environment-token.ts +++ b/packages/@aws-cdk/cdk/test/test.cross-environment-token.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { App, CfnOutput, Construct, PhysicalName, Resource, ResourceIdentifiers, Stack } from '../lib'; +import { App, CfnOutput, Construct, PhysicalName, Resource, Stack } from '../lib'; import { toCloudFormation } from './util'; // tslint:disable:object-literal-key-quotes @@ -196,14 +196,14 @@ class MyResource extends Resource { physicalName, }); - const resourceIdentifiers = new ResourceIdentifiers(this, { + const resourceIdentifiers = this.getCrossEnvironmentAttributes({ arn: 'simple-arn', name: 'simple-name', arnComponents: { region: '', account: '', resource: 'my-resource', - resourceName: this.physicalName.value, + resourceName: this.physicalName, service: 'myservice', }, }); diff --git a/packages/@aws-cdk/cdk/test/test.duration.ts b/packages/@aws-cdk/cdk/test/test.duration.ts new file mode 100644 index 0000000000000..fda0d586bcc78 --- /dev/null +++ b/packages/@aws-cdk/cdk/test/test.duration.ts @@ -0,0 +1,111 @@ +import nodeunit = require('nodeunit'); +import { Duration, Stack, Token } from '../lib'; + +export = nodeunit.testCase({ + 'negative amount'(test: nodeunit.Test) { + test.throws(() => Duration.seconds(-1), /negative/); + + test.done(); + }, + + 'unresolved amount'(test: nodeunit.Test) { + const stack = new Stack(); + const lazyDuration = Duration.seconds(Token.asNumber({ resolve: () => 1337 })); + test.equals(stack.resolve(lazyDuration.toSeconds()), 1337); + test.throws( + () => stack.resolve(lazyDuration.toMinutes()), + /Unable to perform time unit conversion on un-resolved token/ + ); + + test.done(); + }, + + 'Duration in seconds'(test: nodeunit.Test) { + const duration = Duration.seconds(300); + + test.equal(duration.toSeconds(), 300); + test.equal(duration.toMinutes(), 5); + test.throws(() => duration.toDays(), /'300 seconds' cannot be converted into a whole number of days/); + floatEqual(test, duration.toDays({ integral: false }), 300 / 86_400); + + test.equal(Duration.seconds(60 * 60 * 24).toDays(), 1); + + test.done(); + }, + + 'Duration in minutes'(test: nodeunit.Test) { + const duration = Duration.minutes(5); + + test.equal(duration.toSeconds(), 300); + test.equal(duration.toMinutes(), 5); + test.throws(() => duration.toDays(), /'5 minutes' cannot be converted into a whole number of days/); + floatEqual(test, duration.toDays({ integral: false }), 300 / 86_400); + + test.equal(Duration.minutes(60 * 24).toDays(), 1); + + test.done(); + }, + + 'Duration in hours'(test: nodeunit.Test) { + const duration = Duration.hours(5); + + test.equal(duration.toSeconds(), 18_000); + test.equal(duration.toMinutes(), 300); + test.throws(() => duration.toDays(), /'5 hours' cannot be converted into a whole number of days/); + floatEqual(test, duration.toDays({ integral: false }), 5 / 24); + + test.equal(Duration.hours(24).toDays(), 1); + + test.done(); + }, + + 'Duration in days'(test: nodeunit.Test) { + const duration = Duration.days(1); + + test.equal(duration.toSeconds(), 86_400); + test.equal(duration.toMinutes(), 1_440); + test.equal(duration.toDays(), 1); + + test.done(); + }, + + 'toISOString'(test: nodeunit.Test) { + test.equal(Duration.seconds(0).toISOString(), 'PT0S'); + test.equal(Duration.minutes(0).toISOString(), 'PT0S'); + test.equal(Duration.hours(0).toISOString(), 'PT0S'); + test.equal(Duration.days(0).toISOString(), 'PT0S'); + + test.equal(Duration.seconds(5).toISOString(), 'PT5S'); + test.equal(Duration.minutes(5).toISOString(), 'PT5M'); + test.equal(Duration.hours(5).toISOString(), 'PT5H'); + test.equal(Duration.days(5).toISOString(), 'PT5D'); + + test.equal(Duration.seconds(1 + 60 * (1 + 60 * (1 + 24))).toISOString(), 'PT1D1H1M1S'); + + test.done(); + }, + + 'parse'(test: nodeunit.Test) { + test.equal(Duration.parse('PT0S').toSeconds(), 0); + test.equal(Duration.parse('PT0M').toSeconds(), 0); + test.equal(Duration.parse('PT0H').toSeconds(), 0); + test.equal(Duration.parse('PT0D').toSeconds(), 0); + + test.equal(Duration.parse('PT5S').toSeconds(), 5); + test.equal(Duration.parse('PT5M').toSeconds(), 300); + test.equal(Duration.parse('PT5H').toSeconds(), 18_000); + test.equal(Duration.parse('PT5D').toSeconds(), 432_000); + + test.equal(Duration.parse('PT1D1H1M1S').toSeconds(), 1 + 60 * (1 + 60 * (1 + 24))); + + test.done(); + } +}); + +function floatEqual(test: nodeunit.Test, actual: number, expected: number) { + test.ok( + // Floats are subject to rounding errors up to Number.ESPILON + actual >= expected - Number.EPSILON && actual <= expected + Number.EPSILON, + `${actual} == ${expected}`, + ); +} diff --git a/packages/@aws-cdk/cdk/test/test.fn.ts b/packages/@aws-cdk/cdk/test/test.fn.ts index bbec8b7acf8ef..3be13b3ad6ea5 100644 --- a/packages/@aws-cdk/cdk/test/test.fn.ts +++ b/packages/@aws-cdk/cdk/test/test.fn.ts @@ -126,6 +126,19 @@ export = nodeunit.testCase({ { verbose: true } ); }), + 'Fn::EachMemberIn': asyncTest(async (test) => { + const stack = new Stack(); + const eachMemberIn = Fn.conditionEachMemberIn( + Fn.valueOfAll('AWS::EC2::Subnet::Id', 'VpcId'), + Fn.refAll('AWS::EC2::VPC::Id') + ); + test.deepEqual(stack.resolve(eachMemberIn), { + 'Fn::EachMemberIn': [ + { 'Fn::ValueOfAll': ['AWS::EC2::Subnet::Id', 'VpcId'] }, + { 'Fn::RefAll': 'AWS::EC2::VPC::Id'} + ] + }); + }), }, }); diff --git a/packages/@aws-cdk/cdk/test/test.include.ts b/packages/@aws-cdk/cdk/test/test.include.ts index f1637c9a2a34d..14743bc3ab006 100644 --- a/packages/@aws-cdk/cdk/test/test.include.ts +++ b/packages/@aws-cdk/cdk/test/test.include.ts @@ -1,12 +1,12 @@ import { Test } from 'nodeunit'; -import { CfnOutput, CfnParameter, CfnResource, Include, Stack } from '../lib'; +import { CfnInclude, CfnOutput, CfnParameter, CfnResource, Stack } from '../lib'; import { toCloudFormation } from './util'; export = { 'the Include construct can be used to embed an existing template as-is into a stack'(test: Test) { const stack = new Stack(); - new Include(stack, 'T1', { template: clone(template) }); + new CfnInclude(stack, 'T1', { template: clone(template) }); test.deepEqual(toCloudFormation(stack), { Parameters: { MyParam: { Type: 'String', Default: 'Hello' } }, @@ -20,9 +20,9 @@ export = { 'included templates can co-exist with elements created programmatically'(test: Test) { const stack = new Stack(); - new Include(stack, 'T1', { template: clone(template) }); + new CfnInclude(stack, 'T1', { template: clone(template) }); new CfnResource(stack, 'MyResource3', { type: 'ResourceType3', properties: { P3: 'Hello' } }); - new CfnOutput(stack, 'MyOutput', { description: 'Out!', disableExport: true, value: 'hey' }); + new CfnOutput(stack, 'MyOutput', { description: 'Out!', value: 'hey' }); new CfnParameter(stack, 'MyParam2', { type: 'Integer' }); test.deepEqual(toCloudFormation(stack), { @@ -42,7 +42,7 @@ export = { 'exception is thrown in construction if an entity from an included template has the same id as a programmatic entity'(test: Test) { const stack = new Stack(); - new Include(stack, 'T1', { template }); + new CfnInclude(stack, 'T1', { template }); new CfnResource(stack, 'MyResource3', { type: 'ResourceType3', properties: { P3: 'Hello' } }); new CfnOutput(stack, 'MyOutput', { description: 'Out!', value: 'in' }); new CfnParameter(stack, 'MyParam', { type: 'Integer' }); // duplicate! diff --git a/packages/@aws-cdk/cdk/test/test.output.ts b/packages/@aws-cdk/cdk/test/test.output.ts index 3d75fd53840cf..165284fdd2a14 100644 --- a/packages/@aws-cdk/cdk/test/test.output.ts +++ b/packages/@aws-cdk/cdk/test/test.output.ts @@ -9,7 +9,7 @@ export = { const ref = res.ref; new CfnOutput(stack, 'MyOutput', { - export: 'ExportName', + exportName: 'ExportName', value: ref, description: 'CfnOutput properties' }); @@ -22,45 +22,6 @@ export = { test.done(); }, - 'disableExport can be used to disable the auto-export behavior'(test: Test) { - const stack = new Stack(); - const output = new CfnOutput(stack, 'MyOutput', { disableExport: true, value: 'boom' }); - - test.equal(output.export, null); - - // cannot specify `export` and `disableExport` at the same time. - test.throws(() => new CfnOutput(stack, 'YourOutput', { - disableExport: true, - export: 'bla', - value: 'boom' - }), /Cannot set `disableExport` and specify an export name/); - - test.done(); - }, - - 'if stack name is undefined, we will only use the logical ID for the export name'(test: Test) { - const stack = new Stack(); - const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); - test.deepEqual(stack.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'Stack:MyOutput' }); - test.done(); - }, - - 'makeImportValue can be used to create an Fn::ImportValue from an output'(test: Test) { - const stack = new Stack(undefined, 'MyStack'); - const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); - test.deepEqual(stack.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); - - test.deepEqual(toCloudFormation(stack), { - Outputs: { - MyOutput: { - Value: 'boom', - Export: { Name: 'MyStack:MyOutput' } - } - } - }); - test.done(); - }, - 'No export is created by default'(test: Test) { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/cdk/test/test.resource.ts b/packages/@aws-cdk/cdk/test/test.resource.ts index 86580aca811a2..7547b3e582240 100644 --- a/packages/@aws-cdk/cdk/test/test.resource.ts +++ b/packages/@aws-cdk/cdk/test/test.resource.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { App, App as Root, CfnCondition, - CfnResource, Construct, ConstructNode, DeletionPolicy, + CfnDeletionPolicy, CfnResource, Construct, ConstructNode, Fn, RemovalPolicy, Stack } from '../lib'; import { toCloudFormation } from './util'; @@ -215,8 +215,8 @@ export = { beforeAllowTrafficHook: 'lambda1', }, }; - r1.options.deletionPolicy = DeletionPolicy.Retain; - r1.options.updateReplacePolicy = DeletionPolicy.Snapshot; + r1.options.deletionPolicy = CfnDeletionPolicy.Retain; + r1.options.updateReplacePolicy = CfnDeletionPolicy.Snapshot; test.deepEqual(toCloudFormation(stack), { Resources: { @@ -667,7 +667,7 @@ export = { resB.node.addDependency(resA); // THEN - const assembly = app.run(); + const assembly = app.synth(); const templateB = assembly.getStack(stackB.stackName).template; test.deepEqual(templateB, { diff --git a/packages/@aws-cdk/cdk/test/test.stack.ts b/packages/@aws-cdk/cdk/test/test.stack.ts index 7ea682d5d71b7..340ebd3253f3d 100644 --- a/packages/@aws-cdk/cdk/test/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/test.stack.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { App, CfnCondition, CfnOutput, CfnParameter, CfnResource, Construct, ConstructNode, Include, Lazy, ScopedAws, Stack } from '../lib'; +import { App, CfnCondition, CfnInclude, CfnOutput, CfnParameter, CfnResource, Construct, ConstructNode, Lazy, ScopedAws, Stack } from '../lib'; import { Intrinsic } from '../lib/private/intrinsic'; import { toCloudFormation } from './util'; @@ -120,7 +120,7 @@ export = { Description: 'hello, world' }; - new Include(stack, 'Include', { template }); + new CfnInclude(stack, 'Include', { template }); const output = toCloudFormation(stack); @@ -356,7 +356,7 @@ export = { // { Ref } and { GetAtt } new CfnResource(stack, 'RefToBonjour', { type: 'Other::Resource', properties: { - RefToBonjour: bonjour.refAsString, + RefToBonjour: bonjour.ref, GetAttBonjour: bonjour.getAtt('TheAtt').toString() }}); diff --git a/packages/@aws-cdk/cloudformation-diff/package-lock.json b/packages/@aws-cdk/cloudformation-diff/package-lock.json index 4b0f41a3a7324..a0ebbfa0e4c63 100644 --- a/packages/@aws-cdk/cloudformation-diff/package-lock.json +++ b/packages/@aws-cdk/cloudformation-diff/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts index 912eceabeb013..cc48c79cdacc7 100644 --- a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts +++ b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts @@ -218,8 +218,8 @@ export class CloudAssemblyBuilder { export interface AssemblyBuildOptions { /** - * Include runtime information (module versions) in manifest. - * @default true + * Include the specified runtime information (module versions) in manifest. + * @default - if this option is not specified, runtime info will not be included */ readonly runtimeInfo?: RuntimeInfo; } diff --git a/packages/@aws-cdk/cx-api/package-lock.json b/packages/@aws-cdk/cx-api/package-lock.json index 0c08a619d29d9..e24d294c1bf3f 100644 --- a/packages/@aws-cdk/cx-api/package-lock.json +++ b/packages/@aws-cdk/cx-api/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cx-api", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/runtime-values/.gitignore b/packages/@aws-cdk/runtime-values/.gitignore deleted file mode 100644 index 94953ee0cd7f6..0000000000000 --- a/packages/@aws-cdk/runtime-values/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -*.js -tsconfig.json -tslint.json -*.js.map -*.d.ts -dist -lib/generated/resources.ts -.nyc_output -coverage -.jsii - -.LAST_BUILD -.nycrc -.LAST_PACKAGE -*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/runtime-values/.jsii b/packages/@aws-cdk/runtime-values/.jsii new file mode 100644 index 0000000000000..baae0e8a44f05 --- /dev/null +++ b/packages/@aws-cdk/runtime-values/.jsii @@ -0,0 +1,53 @@ +{ + "author": { + "name": "Amazon Web Services", + "organization": true, + "roles": [ + "author" + ], + "url": "https://aws.amazon.com" + }, + "description": "Runtime values support for the AWS CDK", + "docs": { + "deprecated": "This module has been deprecated. Use environment variables or SSM parameters to publish values to runtime code", + "stability": "deprecated" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "jsiiVersion": "0.11.2 (build 27d16c2)", + "license": "Apache-2.0", + "name": "@aws-cdk/runtime-values", + "readme": { + "markdown": "## Runtime Values\n\n\n---\n\n![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge)\n\n> This API may emit warnings. Backward compatibility is not guaranteed.\n\n---\n\n\nThis module has been deprecated. Use environment variables or SSM parameters to publish values to runtime code.\n\n" + }, + "repository": { + "directory": "packages/@aws-cdk/runtime-values", + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "schema": "jsii/0.10.0", + "targets": { + "dotnet": { + "assemblyOriginatorKeyFile": "../../key.snk", + "namespace": "Amazon.CDK.RuntimeValues", + "packageId": "Amazon.CDK.RuntimeValues", + "signAssembly": true + }, + "java": { + "maven": { + "artifactId": "cdk-runtime-values", + "groupId": "software.amazon.awscdk" + }, + "package": "software.amazon.awscdk.runtimevalues" + }, + "js": { + "npm": "@aws-cdk/runtime-values" + }, + "python": { + "distName": "aws-cdk.runtime-values", + "module": "aws_cdk.runtime_values" + } + }, + "types": {}, + "version": "0.34.0", + "fingerprint": "oXizSYyKcsmi8DG/BP74CJob3TgH1K4FJTDBXCXVrZ0=" +} diff --git a/packages/@aws-cdk/runtime-values/.npmignore b/packages/@aws-cdk/runtime-values/.npmignore deleted file mode 100644 index f5a63a96df103..0000000000000 --- a/packages/@aws-cdk/runtime-values/.npmignore +++ /dev/null @@ -1,18 +0,0 @@ -# Don't include original .ts files when doing `npm pack` -*.ts -!*.d.ts -coverage -.nyc_output -*.tgz - -dist -.LAST_PACKAGE -.LAST_BUILD -!*.js - -# Include .jsii -!.jsii - -*.snk - -*.tsbuildinfo diff --git a/packages/@aws-cdk/runtime-values/LICENSE b/packages/@aws-cdk/runtime-values/LICENSE deleted file mode 100644 index 46c185646b439..0000000000000 --- a/packages/@aws-cdk/runtime-values/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2018-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. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/@aws-cdk/runtime-values/NOTICE b/packages/@aws-cdk/runtime-values/NOTICE deleted file mode 100644 index 8585168af8b7d..0000000000000 --- a/packages/@aws-cdk/runtime-values/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ -AWS Cloud Development Kit (AWS CDK) -Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/runtime-values/README.md b/packages/@aws-cdk/runtime-values/README.md deleted file mode 100644 index 241862f1733a0..0000000000000 --- a/packages/@aws-cdk/runtime-values/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## Runtime Values - - ---- - -![Stability: Deprecated](https://img.shields.io/badge/stability-Deprecated-critical.svg?style=for-the-badge) - -> This API may emit warnings. Backward compatibility is not guaranteed. - ---- - - -This module has been deprecated. Use environment variables or SSM parameters to publish values to runtime code. - diff --git a/packages/@aws-cdk/runtime-values/lib/index.d.ts b/packages/@aws-cdk/runtime-values/lib/index.d.ts new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/runtime-values/lib/index.js b/packages/@aws-cdk/runtime-values/lib/index.js new file mode 100644 index 0000000000000..8402aeb525f92 --- /dev/null +++ b/packages/@aws-cdk/runtime-values/lib/index.js @@ -0,0 +1,4 @@ +"use strict"; +// tslint:disable-next-line:no-console +console.error(`ERROR: @aws-cdk/runtime-values is deprecated`); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsc0NBQXNDO0FBQ3RDLE9BQU8sQ0FBQyxLQUFLLENBQUMsOENBQThDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIHRzbGludDpkaXNhYmxlLW5leHQtbGluZTpuby1jb25zb2xlXG5jb25zb2xlLmVycm9yKGBFUlJPUjogQGF3cy1jZGsvcnVudGltZS12YWx1ZXMgaXMgZGVwcmVjYXRlZGApO1xuIl19 \ No newline at end of file diff --git a/packages/@aws-cdk/runtime-values/lib/index.ts b/packages/@aws-cdk/runtime-values/lib/index.ts deleted file mode 100644 index 305f9aaf4ccb1..0000000000000 --- a/packages/@aws-cdk/runtime-values/lib/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// tslint:disable-next-line:no-console -console.error(`ERROR: @aws-cdk/runtime-values is deprecated`); diff --git a/packages/@aws-cdk/runtime-values/package-lock.json b/packages/@aws-cdk/runtime-values/package-lock.json deleted file mode 100644 index 776ce899976b5..0000000000000 --- a/packages/@aws-cdk/runtime-values/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@aws-cdk/runtime-values", - "version": "0.33.0", - "lockfileVersion": 1 -} diff --git a/packages/@aws-cdk/runtime-values/package.json b/packages/@aws-cdk/runtime-values/package.json deleted file mode 100644 index e190981e0f857..0000000000000 --- a/packages/@aws-cdk/runtime-values/package.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "@aws-cdk/runtime-values", - "version": "0.35.0", - "description": "Runtime values support for the AWS CDK", - "deprecated": "This module has been deprecated. Use environment variables or SSM parameters to publish values to runtime code", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "jsii": { - "outdir": "dist", - "targets": { - "java": { - "package": "software.amazon.awscdk.runtimevalues", - "maven": { - "groupId": "software.amazon.awscdk", - "artifactId": "cdk-runtime-values" - } - }, - "dotnet": { - "namespace": "Amazon.CDK.RuntimeValues", - "packageId": "Amazon.CDK.RuntimeValues", - "signAssembly": true, - "assemblyOriginatorKeyFile": "../../key.snk" - }, - "python": { - "distName": "aws-cdk.runtime-values", - "module": "aws_cdk.runtime_values" - } - } - }, - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git", - "directory": "packages/@aws-cdk/runtime-values" - }, - "scripts": { - "build": "cdk-build", - "watch": "cdk-watch", - "lint": "cdk-lint", - "test": "cdk-test", - "pkglint": "pkglint -f", - "integ": "cdk-integ", - "package": "cdk-package", - "awslint": "cdk-awslint", - "build+test+package": "npm run build+test && npm run package", - "build+test": "npm run build && npm test" - }, - "keywords": [ - "aws", - "cdk", - "runtime-values" - ], - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "devDependencies": { - "cdk-build-tools": "^0.35.0", - "cdk-integ-tools": "^0.35.0", - "pkglint": "^0.35.0" - }, - "homepage": "https://github.com/awslabs/aws-cdk", - "engines": { - "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "props-no-any:@aws-cdk/runtime-values.RuntimeValueProps.value" - ] - }, - "stability": "deprecated" -} diff --git a/packages/@aws-cdk/runtime-values/tsconfig.json b/packages/@aws-cdk/runtime-values/tsconfig.json new file mode 100644 index 0000000000000..be137c10a984b --- /dev/null +++ b/packages/@aws-cdk/runtime-values/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "alwaysStrict": true, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2016", + "es2017.object", + "es2017.string" + ], + "module": "CommonJS", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "strict": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + "stripInternal": true, + "target": "ES2018", + "composite": true + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "../../../tools/cdk-build-tools" + }, + { + "path": "../../../tools/pkglint" + } + ], + "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" +} diff --git a/packages/aws-cdk/lib/archive.ts b/packages/aws-cdk/lib/archive.ts index c924035bca369..a5994ea035369 100644 --- a/packages/aws-cdk/lib/archive.ts +++ b/packages/aws-cdk/lib/archive.ts @@ -1,26 +1,39 @@ import archiver = require('archiver'); import crypto = require('crypto'); import fs = require('fs-extra'); +import glob = require('glob'); +import path = require('path'); export function zipDirectory(directory: string, outputFile: string): Promise { return new Promise((ok, fail) => { - const output = fs.createWriteStream(outputFile); - const archive = archiver('zip'); // The below options are needed to support following symlinks when building zip files: - // - nodir: This will prevent symlinks themselves from being copied into the zip. + // - nodir: This will prevent symlinks themselves from being copied into the zip. // - follow: This will follow symlinks and copy the files within. const globOptions = { dot: true, nodir: true, follow: true, - cwd: directory + cwd: directory, }; - archive.glob('**', globOptions); - archive.pipe(output); - archive.finalize(); + const files = glob.sync('**', globOptions); // The output here is already sorted + + const output = fs.createWriteStream(outputFile); + const archive = archiver('zip'); archive.on('warning', fail); archive.on('error', fail); + archive.pipe(output); + + files.forEach(file => { // Append files serially to ensure file order + archive.append(fs.createReadStream(path.join(directory, file)), { + name: file, + date: new Date('1980-01-01T00:00:00.000Z'), // reset dates to get the same hash for the same content + }); + }); + + archive.finalize(); + + // archive has been finalized and the output file descriptor has closed, resolve promise output.once('close', () => ok()); }); } diff --git a/packages/aws-cdk/lib/serialize.ts b/packages/aws-cdk/lib/serialize.ts index 3219cb5a48836..70f0550ddfbba 100644 --- a/packages/aws-cdk/lib/serialize.ts +++ b/packages/aws-cdk/lib/serialize.ts @@ -1,10 +1,19 @@ import YAML = require('yaml'); +// tslint:disable-next-line: no-var-requires +const yamlTypes = require('yaml/types'); + /** * Stringify to YAML */ export function toYAML(obj: any): string { + const oldFold = yamlTypes.strOptions.fold.lineWidth; + try { + yamlTypes.strOptions.fold.lineWidth = 0; return YAML.stringify(obj, { schema: 'yaml-1.1' }); + } finally { + yamlTypes.strOptions.fold.lineWidth = oldFold; + } } /** @@ -35,4 +44,4 @@ export function serializeStructure(object: any, json: boolean) { } else { return toYAML(object); } -} \ No newline at end of file +} diff --git a/packages/aws-cdk/package-lock.json b/packages/aws-cdk/package-lock.json index 3dc9ca939af29..5cf170ac1a8ce 100644 --- a/packages/aws-cdk/package-lock.json +++ b/packages/aws-cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-cdk", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -107,6 +107,15 @@ "@types/node": "*" } }, + "@types/jszip": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.1.6.tgz", + "integrity": "sha512-m8uFcI+O2EupCfbEVQWsBM/4nhbegjOHL7cQgBpM95FeF98kdFJXzy9/8yhx4b3lCRl/gMBhcvyh30Qt3X+XPQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -990,6 +999,12 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1098,6 +1113,18 @@ "verror": "1.10.0" } }, + "jszip": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", + "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, "just-extend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", @@ -1129,6 +1156,15 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -1388,6 +1424,12 @@ "thunkify": "^2.1.2" } }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -1608,6 +1650,12 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 341f535d29d20..864715ba187a0 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@types/archiver": "^3.0.0", "@types/fs-extra": "^7.0.0", + "@types/jszip": "^3.1.6", "@types/minimatch": "^3.0.3", "@types/mockery": "^1.4.29", "@types/request": "^2.48.1", @@ -46,6 +47,7 @@ "@types/yaml": "^1.0.2", "@types/yargs": "^13.0.0", "cdk-build-tools": "^0.35.0", + "jszip": "^3.2.1", "mockery": "^2.1.0", "pkglint": "^0.35.0", "sinon": "^7.3.2" @@ -60,6 +62,7 @@ "colors": "^1.3.3", "decamelize": "^3.2.0", "fs-extra": "^8.0.1", + "glob": "^7.1.4", "json-diff": "^0.5.4", "minimatch": ">=3.0", "promptly": "^3.0.3", diff --git a/packages/aws-cdk/test/test.archive.ts b/packages/aws-cdk/test/test.archive.ts index f26b662e79ca2..c709cce749765 100644 --- a/packages/aws-cdk/test/test.archive.ts +++ b/packages/aws-cdk/test/test.archive.ts @@ -1,5 +1,6 @@ import { exec as _exec } from 'child_process'; import fs = require('fs-extra'); +import jszip = require('jszip'); import { Test } from 'nodeunit'; import os = require('os'); import path = require('path'); @@ -24,6 +25,13 @@ export = { test.ok(false, `extracted directory ${extractDir} differs from original ${originalDir}`); } + // inspect the zile file to check that dates are reset + const zip = await fs.readFile(zipFile); + const zipData = await jszip.loadAsync(zip); + const dates = Object.values(zipData.files).map(file => file.date.toISOString()); + test.equal(dates[0], '1980-01-01T00:00:00.000Z', 'Dates are not reset'); + test.equal(new Set(dates).size, 1, 'Dates are not equal'); + await fs.remove(stagingDir); await fs.remove(extractDir); test.done(); diff --git a/packages/aws-cdk/test/test.serialize.ts b/packages/aws-cdk/test/test.serialize.ts new file mode 100644 index 0000000000000..54b1d1db6c3cc --- /dev/null +++ b/packages/aws-cdk/test/test.serialize.ts @@ -0,0 +1,12 @@ +import nodeunit = require('nodeunit'); +import { toYAML } from '../lib/serialize'; + +export = nodeunit.testCase({ + toYAML: { + 'does not wrap lines'(test: nodeunit.Test) { + const longString = 'Long string is long!'.repeat(1_024); + test.equal(toYAML({ longString }), `longString: ${longString}\n`); + test.done(); + } + } +}); diff --git a/packages/aws-cdk/tsconfig.json b/packages/aws-cdk/tsconfig.json index 0c02dab2c6a45..0eac39a6083af 100644 --- a/packages/aws-cdk/tsconfig.json +++ b/packages/aws-cdk/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target":"ES2018", "module": "commonjs", - "lib": ["es2016", "es2017.object", "es2017.string"], + "lib": ["es2016", "es2017.object", "es2017.string", "dom"], "declaration": true, "strict": true, "noImplicitAny": true, diff --git a/packages/cdk-dasm/package-lock.json b/packages/cdk-dasm/package-lock.json index 47b64dc22cfc4..d79b9b2105f3a 100644 --- a/packages/cdk-dasm/package-lock.json +++ b/packages/cdk-dasm/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-dasm", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/decdk/examples/lambda-events.json b/packages/decdk/examples/lambda-events.json index 80d367b1e02c6..b65fd390e5f6b 100644 --- a/packages/decdk/examples/lambda-events.json +++ b/packages/decdk/examples/lambda-events.json @@ -9,9 +9,9 @@ "Properties": { "partitionKey": { "name": "ID", - "type": "String" + "type": "STRING" }, - "stream": "NewAndOldImages" + "stream": "NEW_AND_OLD_IMAGES" } }, "HelloWorldFunction": { @@ -26,7 +26,7 @@ "Param": "f" }, "events": [ - { "@aws-cdk/aws-lambda-event-sources.DynamoEventSource": { "table": { "Ref": "Table" }, "startingPosition": "TrimHorizon" } }, + { "@aws-cdk/aws-lambda-event-sources.DynamoEventSource": { "table": { "Ref": "Table" }, "startingPosition": "TRIM_HORIZON" } }, { "@aws-cdk/aws-lambda-event-sources.ApiEventSource": { "method": "GET", "path": "/hello" } }, { "@aws-cdk/aws-lambda-event-sources.ApiEventSource": { "method": "POST", "path": "/hello" } }, { "@aws-cdk/aws-lambda-event-sources.SnsEventSource": { "topic": { "Ref": "MyTopic" } } } diff --git a/packages/decdk/examples/queue-kms.json b/packages/decdk/examples/queue-kms.json index 4d46ee19b1ec4..50f7da4af5379 100644 --- a/packages/decdk/examples/queue-kms.json +++ b/packages/decdk/examples/queue-kms.json @@ -4,7 +4,7 @@ "MyQueue": { "Type": "@aws-cdk/aws-sqs.Queue", "Properties": { - "encryption": "Kms" + "encryption": "KMS" } } } diff --git a/packages/decdk/lib/declarative-stack.ts b/packages/decdk/lib/declarative-stack.ts index 4f6e1ad06931a..c1ec3a1637131 100644 --- a/packages/decdk/lib/declarative-stack.ts +++ b/packages/decdk/lib/declarative-stack.ts @@ -50,7 +50,7 @@ export class DeclarativeStack extends cdk.Stack { delete template.$schema; // Add an Include construct with what's left of the template - new cdk.Include(this, 'Include', { template }); + new cdk.CfnInclude(this, 'Include', { template }); // replace all "Fn::GetAtt" with tokens that resolve correctly both for // constructs and raw resources. @@ -385,7 +385,7 @@ function deconstructGetAtt(stack: cdk.Stack, id: string, attribute: string) { return cdk.Lazy.stringValue({ produce: () => { const res = stack.node.tryFindChild(id); if (!res) { - const include = stack.node.tryFindChild('Include') as cdk.Include; + const include = stack.node.tryFindChild('Include') as cdk.CfnInclude; if (!include) { throw new Error(`Unexpected - "Include" should be in the stack at this point`); } @@ -411,7 +411,7 @@ function findConstruct(stack: cdk.Stack, id: string) { } function processReferences(stack: cdk.Stack) { - const include = stack.node.findChild('Include') as cdk.Include; + const include = stack.node.findChild('Include') as cdk.CfnInclude; if (!include) { throw new Error('Unexpected'); } diff --git a/packages/decdk/package-lock.json b/packages/decdk/package-lock.json index ba59ddef9477b..4f4d526095403 100644 --- a/packages/decdk/package-lock.json +++ b/packages/decdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "decdk", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index bf327752cb659..12f454af5a20e 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -1227,21 +1227,6 @@ Object { }, "Type": "AWS::Lambda::Permission", }, - "HelloWorldFunctionMyTopicSubscription20D3FA87": Object { - "Properties": Object { - "Endpoint": Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionB2AB6E79", - "Arn", - ], - }, - "Protocol": "lambda", - "TopicArn": Object { - "Ref": "MyTopic86869434", - }, - }, - "Type": "AWS::SNS::Subscription", - }, "HelloWorldFunctionServiceRole8E0BD458": Object { "Properties": Object { "AssumeRolePolicyDocument": Object { @@ -1321,6 +1306,21 @@ Object { "MyTopic86869434": Object { "Type": "AWS::SNS::Topic", }, + "MyTopicHelloWorldFunction831B106E": Object { + "Properties": Object { + "Endpoint": Object { + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], + }, + "Protocol": "lambda", + "TopicArn": Object { + "Ref": "MyTopic86869434", + }, + }, + "Type": "AWS::SNS::Subscription", + }, "TableCD117FA1": Object { "Properties": Object { "AttributeDefinitions": Array [ @@ -1659,7 +1659,10 @@ Object { }, "Type": "AWS::Lambda::Permission", }, - "LambdaTopicSubscriptionF6CC623D": Object { + "TopicBFC7AF6E": Object { + "Type": "AWS::SNS::Topic", + }, + "TopicLambda3DD31D45": Object { "Properties": Object { "Endpoint": Object { "Fn::GetAtt": Array [ @@ -1674,9 +1677,6 @@ Object { }, "Type": "AWS::SNS::Subscription", }, - "TopicBFC7AF6E": Object { - "Type": "AWS::SNS::Topic", - }, }, } `; diff --git a/tools/awslint/README.md b/tools/awslint/README.md index fc5511dbe593c..585a2b79a86ed 100644 --- a/tools/awslint/README.md +++ b/tools/awslint/README.md @@ -16,19 +16,19 @@ For example: ```console $ cd @aws-cdk/aws-sns -$ awslint -@aws-cdk/aws-sns.ISubscription -- warning: every resource must have a resource interface [resource-interface] -@aws-cdk/aws-sns.ITopicPolicy -- warning: every resource must have a resource interface [resource-interface] +$ npm run awslint +warning: [awslint:resource-interface:@aws-cdk/aws-sns.Subscription] every resource must have a resource interface +warning: [awslint:resource-interface:@aws-cdk/aws-sns.TopicPolicy] every resource must have a resource interface ``` Each diagnostics includes the following elements: ``` -@aws-cdk/aws-sns.ISubscription -- warning: every resource must have a resource interface [resource-interface] -[----------------------------] [------] [-------------------------------------------] [------------------] - ^ ^ ^ ^ - | | | | - scope level message rule +warning: [awslint:resource-interface:@aws-cdk/aws-sns.Subscription] every resource must have a resource interface +[------] [-------------------------] [----------------------------] [-------------------------------------------] + ^ ^ ^ ^ + | | | | + level rule scope message ``` ## Options and Configuration @@ -46,17 +46,17 @@ via an `awslint` key in the module's `package.json`. ## Include/Exclude -The `--include` and `--exclude` options can be used to specify which rules will +The `i`/`--include` and `-x`/`--exclude` options can be used to specify which rules will be evaluated. For example: ```console # evaluate only the "resource-props" and "import" rules in all scopes -$ awslint -i resource-props -i import +$ npm run awslint -- -i resource-props -i import # evaluate only the "import" rule in all scopes besides ones that begin with "@aws-cdk/aws-s3" -$ awslint -i import -x "*:@aws-cdk/aws-s3*" +$ npm run awslint -- -i import -x "*:@aws-cdk/aws-s3*" ``` @@ -88,7 +88,7 @@ in the module's `package.json` file. This is useful to bootstrap the linting pro progressively fix errors. ```console -$ awslint --save +$ npm run awslint -- --save [shows errors] $ cat package.json @@ -102,7 +102,7 @@ $ cat package.json } } -$ awslint +$ npm run awslint [no errors] ``` @@ -118,7 +118,7 @@ If `--save` is specified, `awslint` will always exit with status code 0. To list all linter rules: ```console -$ awslint list +$ npm run awslint list module-name: module name must be @aws-cdk/aws- construct-ctor: signature of all construct constructors should be "scope, id, props" resource-class: every resource must have a resource class (L2) diff --git a/tools/awslint/bin/awslint.ts b/tools/awslint/bin/awslint.ts index 83d2c5c3e09ac..d34e2d8f3c767 100644 --- a/tools/awslint/bin/awslint.ts +++ b/tools/awslint/bin/awslint.ts @@ -5,8 +5,8 @@ import fs = require('fs-extra'); import reflect = require('jsii-reflect'); import path = require('path'); import yargs = require('yargs'); -import { AggregateLinter, apiLinter, attributesLinter, cfnResourceLinter, constructLinter, DiagnosticLevel, eventsLinter, exportsLinter, importsLinter, - integrationLinter, moduleLinter, resourceLinter } from '../lib'; +import { AggregateLinter, apiLinter, attributesLinter, cfnResourceLinter, constructLinter, DiagnosticLevel, durationsLinter, eventsLinter, exportsLinter, + importsLinter, integrationLinter, moduleLinter, resourceLinter } from '../lib'; const linter = new AggregateLinter( moduleLinter, @@ -18,7 +18,8 @@ const linter = new AggregateLinter( attributesLinter, exportsLinter, eventsLinter, - integrationLinter + integrationLinter, + durationsLinter ); let stackTrace = false; @@ -273,4 +274,4 @@ async function shell(command: string) { }); child.once('error', ko); }); -} \ No newline at end of file +} diff --git a/tools/awslint/lib/rules/core-types.ts b/tools/awslint/lib/rules/core-types.ts index 2f7f9205d3f5e..cd622f477623f 100644 --- a/tools/awslint/lib/rules/core-types.ts +++ b/tools/awslint/lib/rules/core-types.ts @@ -9,7 +9,8 @@ enum CoreTypesFqn { ConstructInterface = "@aws-cdk/cdk.IConstruct", Resource = "@aws-cdk/cdk.Resource", ResourceInterface = "@aws-cdk/cdk.IResource", - ResolvableInterface = "@aws-cdk/cdk.IResolvable" + ResolvableInterface = "@aws-cdk/cdk.IResolvable", + PhysicalName = "@aws-cdk/cdk.PhysicalName" } export class CoreTypes { @@ -112,6 +113,10 @@ export class CoreTypes { return this.sys.findInterface(CoreTypesFqn.ResolvableInterface); } + public get physicalNameClass() { + return this.sys.findClass(CoreTypesFqn.PhysicalName) + } + private readonly sys: TypeSystem; constructor(sys: reflect.TypeSystem) { diff --git a/tools/awslint/lib/rules/durations.ts b/tools/awslint/lib/rules/durations.ts new file mode 100644 index 0000000000000..8a58962ab10e5 --- /dev/null +++ b/tools/awslint/lib/rules/durations.ts @@ -0,0 +1,51 @@ +import { Property } from 'jsii-reflect'; +import { Linter } from '../linter'; + +const DURATION_FQN = '@aws-cdk/cdk.Duration'; +const DURATION_SUFFIX = /(Days|Milli(?:(?:S|s)econd)?s?|Sec(?:ond)?s?)$/; + +export const durationsLinter = new Linter(assm => { + const result = new Array(); + const generatedClassesPrefix = `${assm.name}.Cfn`; + for (const type of assm.types) { + // L1 classes are exempted from this rule, doing basic name matching here... + if (type.fqn.startsWith(generatedClassesPrefix)) { continue; } + if (!type.isClassType() && !type.isDataType() && !type.isInterfaceType()) { continue; } + for (const property of type.allProperties) { + if (isDurationProperty(property)) { + result.push(property); + } + } + } + return result; + + function isDurationProperty(prop: Property): boolean { + const lowerCaseName = prop.name.toLowerCase(); + // No lookbehind in JS regexes, so excluding "*PerSecond" by hand here... + return (DURATION_SUFFIX.test(prop.name) && !/PerSecond$/.test(prop.name)) + || lowerCaseName.endsWith('duration') + || lowerCaseName.endsWith('period') + || lowerCaseName.endsWith('timeout') + || lowerCaseName.endsWith('ttl') + || prop.type.fqn === DURATION_FQN; + } +}); + +durationsLinter.add({ + code: 'duration-prop-type', + message: `property must be typed ${DURATION_FQN}`, + eval: evaluation => { + evaluation.assert(evaluation.ctx.type.fqn === DURATION_FQN, + `${evaluation.ctx.parentType.fqn}.${evaluation.ctx.name}`); + }, +}); + +durationsLinter.add({ + code: 'duration-prop-name', + message: 'property must not use time-unit suffix', + eval: evaluation => { + evaluation.assert(!DURATION_SUFFIX.test(evaluation.ctx.name), + `${evaluation.ctx.parentType.fqn}.${evaluation.ctx.name}`, + `(suggested name: "${evaluation.ctx.name.replace(DURATION_SUFFIX, '')}")`); + }, +}); diff --git a/tools/awslint/lib/rules/index.ts b/tools/awslint/lib/rules/index.ts index 04dffc0af190b..b5fd25c16295a 100644 --- a/tools/awslint/lib/rules/index.ts +++ b/tools/awslint/lib/rules/index.ts @@ -1,10 +1,11 @@ -export * from './construct'; -export * from './module'; -export * from './resource'; -export * from './imports'; -export * from './cfn-resource'; -export * from './attributes'; export * from './api'; -export * from './exports'; +export * from './attributes'; +export * from './cfn-resource'; export * from './cloudwatch-events'; +export * from './construct'; +export * from './durations'; +export * from './exports'; +export * from './imports'; export * from './integrations'; +export * from './module'; +export * from './resource'; diff --git a/tools/awslint/lib/rules/resource.ts b/tools/awslint/lib/rules/resource.ts index 2bc887399a870..167c887dbd2e2 100644 --- a/tools/awslint/lib/rules/resource.ts +++ b/tools/awslint/lib/rules/resource.ts @@ -45,6 +45,7 @@ export class ResourceReflection { public readonly cfn: CfnResourceReflection; public readonly basename: string; // i.e. Bucket public readonly core: CoreTypes; + public readonly physicalNameProp?: reflect.Property; constructor(public readonly construct: ConstructReflection) { this.assembly = construct.classType.assembly; @@ -62,6 +63,17 @@ export class ResourceReflection { this.basename = construct.classType.name; this.fqn = construct.fqn; this.attributes = this.findAttributeProperties(); + this.physicalNameProp = this.findPhysicalNameProp(); + } + + private findPhysicalNameProp() { + if (!this.construct.propsType) { + return undefined; + } + + const resourceName = camelcase(this.cfn.basename); + const physicalNameProp = `${resourceName}Name`; + return this.construct.propsType.allProperties.find(x => x.name === physicalNameProp); } /** @@ -176,6 +188,30 @@ resourceLinter.add({ } }); +resourceLinter.add({ + code: 'props-physical-name', + message: "Every Resource must have a single physical name construction property, " + + "with a name that is an ending substring of Name", + eval: e => { + if (!e.ctx.construct.propsType) { return; } + e.assert(e.ctx.physicalNameProp, e.ctx.construct.propsFqn); + } +}); + +resourceLinter.add({ + code: 'props-physical-name-type', + message: 'The type of the physical name prop should be "PhysicalName" if the name is optional and "string" if it is required', + eval: e => { + if (!e.ctx.physicalNameProp) { return; } + const prop = e.ctx.physicalNameProp; + const expectedType = prop.optional + ? e.ctx.core.physicalNameClass + : 'string'; + + e.assertTypesEqual(e.ctx.sys, prop.type, expectedType, `${e.ctx.construct.propsFqn}.${prop.name}`); + } +}); + function tryResolveCfnResource(resourceClass: reflect.ClassType): CfnResourceReflection | undefined { const sys = resourceClass.system; diff --git a/tools/awslint/package-lock.json b/tools/awslint/package-lock.json index b60555198e198..f3eb956080594 100644 --- a/tools/awslint/package-lock.json +++ b/tools/awslint/package-lock.json @@ -1,6 +1,6 @@ { "name": "awslint", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cdk-build-tools/bin/cdk-test.ts b/tools/cdk-build-tools/bin/cdk-test.ts index 499a4cf770842..3cade302e1d67 100644 --- a/tools/cdk-build-tools/bin/cdk-test.ts +++ b/tools/cdk-build-tools/bin/cdk-test.ts @@ -9,12 +9,6 @@ async function main() { const args = yargs .env('CDK_TEST') .usage('Usage: cdk-test') - .option('jsii-diff', { - type: 'string', - desc: 'Specify a different jsii-diff executable', - default: require.resolve('jsii-diff/bin/jsii-diff'), - defaultDescription: 'jsii-diff provided by node dependencies' - }) .option('jest', { type: 'string', desc: 'Specify a different jest executable', diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index dd452cf3c8e7f..06929ad3175f3 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-build-tools", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cfn2ts/package-lock.json b/tools/cfn2ts/package-lock.json index 10acde5ca5829..9a5bf117a81dd 100644 --- a/tools/cfn2ts/package-lock.json +++ b/tools/cfn2ts/package-lock.json @@ -1,6 +1,6 @@ { "name": "cfn2ts", - "version": "0.34.0", + "version": "0.35.0", "lockfileVersion": 1, "requires": true, "dependencies": {