diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 3116ffd4a844d..3e08711b183e4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -25,6 +25,7 @@ falling prey to the [X/Y problem][2]! - **CDK CLI Version:** - **Module Version:** + - **Node.js Version:** - **OS:** - **Language:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 0386d2dbfb9a6..d060fb6f61a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,50 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.42.0](https://github.com/aws/aws-cdk/compare/v1.41.0...v1.42.0) (2020-05-27) + + +### ⚠ BREAKING CHANGES + +* **cloudtrail:** API signatures of `addS3EventSelectors` and +`addLambdaEventSelectors` have changed. Their parameters are now +strongly typed to accept `IBucket` and `IFunction` respectively. +* **cloudtrail:** `addS3EventSelectors` and `addLambdaEventSelectors` +can no longer be used to configure all S3 data events or all Lambda data +events. Two new APIs `logAllS3DataEvents()` and +`logAllLambdaDataEvents()` have been introduced to achieve this. +* **cloudtrail:** The property `snsTopic` is now of the type `ITopic`. + +### Features + +* **cfnspec:** cloudformation spec v14.4.0 ([#8195](https://github.com/aws/aws-cdk/issues/8195)) ([99e7330](https://github.com/aws/aws-cdk/commit/99e7330fc5fc140964c47d8c6dbaee2b46b382e1)) +* **cloudtrail:** create cloudwatch event without needing to create a Trail ([#8076](https://github.com/aws/aws-cdk/issues/8076)) ([0567a23](https://github.com/aws/aws-cdk/commit/0567a2360ac713e3171c9a82767611174dadb6c6)), closes [#6716](https://github.com/aws/aws-cdk/issues/6716) +* **cloudtrail:** user specified log group ([#8079](https://github.com/aws/aws-cdk/issues/8079)) ([0a3785b](https://github.com/aws/aws-cdk/commit/0a3785b7626633fcbdf26ab793c70f2bc017314b)), closes [#6162](https://github.com/aws/aws-cdk/issues/6162) +* **codeguruprofiler:** ProfilingGroup ([#7895](https://github.com/aws/aws-cdk/issues/7895)) ([995088a](https://github.com/aws/aws-cdk/commit/995088abb00d9c75adbb65845998a8328bb5ba14)) +* **codepipeline:** use a special bootstrapless synthesizer for cross-region support Stacks ([#8091](https://github.com/aws/aws-cdk/issues/8091)) ([575f1db](https://github.com/aws/aws-cdk/commit/575f1db0474327c61c4ac626608c9f443ce231d2)), closes [#8082](https://github.com/aws/aws-cdk/issues/8082) +* **cognito:** user pool - case sensitivity for sign in ([460394f](https://github.com/aws/aws-cdk/commit/460394f3dc4737cee80504d6c8ef106ecc3b67d5)), closes [#7988](https://github.com/aws/aws-cdk/issues/7988) [#7235](https://github.com/aws/aws-cdk/issues/7235) +* **core:** CfnJson enables intrinsics in hash keys ([#8099](https://github.com/aws/aws-cdk/issues/8099)) ([195cd40](https://github.com/aws/aws-cdk/commit/195cd405d9f0869875de2ec78661aee3af2c7c7d)), closes [#8084](https://github.com/aws/aws-cdk/issues/8084) +* **eks:** improve security using IRSA conditions ([#8084](https://github.com/aws/aws-cdk/issues/8084)) ([35a01a0](https://github.com/aws/aws-cdk/commit/35a01a079af40da291007da08af6690c9a81c101)) +* **elbv2:** Supports new types of listener rule conditions ([#7848](https://github.com/aws/aws-cdk/issues/7848)) ([3d30ffa](https://github.com/aws/aws-cdk/commit/3d30ffa38c51ae26686287e993af445ea3067766)), closes [#3888](https://github.com/aws/aws-cdk/issues/3888) +* **secretsmanager:** adds grantWrite to Secret ([#7858](https://github.com/aws/aws-cdk/issues/7858)) ([3fed84b](https://github.com/aws/aws-cdk/commit/3fed84ba9eec3f53c662966e366aa629209b7bf5)) +* **sns:** add support for subscription DLQ in SNS ([383cdb8](https://github.com/aws/aws-cdk/commit/383cdb86effeafdf5d0767ed379b16b3d78a933b)) +* **stepfunctions:** new service integration classes for Lambda, SNS, and SQS ([#7946](https://github.com/aws/aws-cdk/issues/7946)) ([c038848](https://github.com/aws/aws-cdk/commit/c0388483524832ca7863de4ee9c472b8ab39de8e)), closes [#6715](https://github.com/aws/aws-cdk/issues/6715) [#6489](https://github.com/aws/aws-cdk/issues/6489) +* **stepfunctions:** support paths in Pass state ([#8070](https://github.com/aws/aws-cdk/issues/8070)) ([86eac6a](https://github.com/aws/aws-cdk/commit/86eac6af074bf78a921c52d613eca0dd4a514a49)), closes [#7181](https://github.com/aws/aws-cdk/issues/7181) +* **stepfunctions-tasks:** task for starting a job run in AWS Glue ([#8143](https://github.com/aws/aws-cdk/issues/8143)) ([a721e67](https://github.com/aws/aws-cdk/commit/a721e670cdc9888cd67ef1a24021004e18bfd23c)) + + +### Bug Fixes + +* **apigateway:** contextAccountId in AccessLogField incorrectly resolves to requestId ([7b89e80](https://github.com/aws/aws-cdk/commit/7b89e805c716fa73d41cc97fcb728634e7a59136)), closes [#7952](https://github.com/aws/aws-cdk/issues/7952) [#7951](https://github.com/aws/aws-cdk/issues/7951) +* **autoscaling:** add noDevice as a volume type ([#7253](https://github.com/aws/aws-cdk/issues/7253)) ([751958b](https://github.com/aws/aws-cdk/commit/751958b69225fdfc52622781c618f5a77f881fb6)), closes [#7242](https://github.com/aws/aws-cdk/issues/7242) +* **aws-eks:** kubectlEnabled: false conflicts with addNodegroup ([#8119](https://github.com/aws/aws-cdk/issues/8119)) ([8610889](https://github.com/aws/aws-cdk/commit/86108890a51443dc06ec6325038c7b19cbdaee76)), closes [#7993](https://github.com/aws/aws-cdk/issues/7993) +* **cli:** paper cuts ([#8164](https://github.com/aws/aws-cdk/issues/8164)) ([af2ea60](https://github.com/aws/aws-cdk/commit/af2ea60e7ae4aaab17ddd10a9142e1809b4c8246)) +* **dynamodb:** the maximum number of nonKeyAttributes is 100, not 20 ([#8186](https://github.com/aws/aws-cdk/issues/8186)) ([0393528](https://github.com/aws/aws-cdk/commit/03935280f1addef392c9b4460737cce8bb2eb8c9)), closes [#8095](https://github.com/aws/aws-cdk/issues/8095) +* **eks:** unable to add multiple service accounts ([#8122](https://github.com/aws/aws-cdk/issues/8122)) ([524440c](https://github.com/aws/aws-cdk/commit/524440c5454d15276c92581a08d4ee7cad1790eb)) +* **events:** cannot use the same target account for 2 cross-account event sources ([#8068](https://github.com/aws/aws-cdk/issues/8068)) ([395c07c](https://github.com/aws/aws-cdk/commit/395c07c0cac7739743fc71d71fddd8880b608ead)), closes [#8010](https://github.com/aws/aws-cdk/issues/8010) +* **lambda-nodejs:** build fails on Windows ([#8140](https://github.com/aws/aws-cdk/issues/8140)) ([04490b1](https://github.com/aws/aws-cdk/commit/04490b134a05ec34523541a3ca282ba8957a7964)), closes [#8107](https://github.com/aws/aws-cdk/issues/8107) +* **cloudtrail:** better typed event selector apis ([#8097](https://github.com/aws/aws-cdk/issues/8097)) ([0028778](https://github.com/aws/aws-cdk/commit/0028778c0f00f2faa8dad25345cd17f311fad5da)) + ## [1.41.0](https://github.com/aws/aws-cdk/compare/v1.40.0...v1.41.0) (2020-05-21) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddd76ca329bbf..ad4328ebde45a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,8 @@ you need to have the following SDKs and tools locally: - We recommend using a version in [Active LTS](https://nodejs.org/en/about/releases/) - ⚠️ versions `13.0.0` to `13.6.0` are not supported due to compatibility issues with our dependencies. - [Yarn >= 1.19.1](https://yarnpkg.com/lang/en/docs/install) -- [Java OpenJDK 8](http://openjdk.java.net/install/) +- [Java OpenJDK 8](https://docs.aws.amazon.com/corretto/latest/corretto-8-ug/downloads-list.html) +- [Apache Maven](http://maven.apache.org/install.html) - [.NET Core SDK 3.1](https://www.microsoft.com/net/download) - [Python 3.6.5](https://www.python.org/downloads/release/python-365/) - [Ruby 2.5.1](https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/) @@ -91,6 +92,13 @@ $ yarn install $ yarn build ``` +If you get compiler errors when building, a common cause is globally installed tools like tslint and typescript. Try uninstalling them. + +``` +npm uninstall -g tslint +npm uninstall -g typescript +``` + Alternatively, the [Full Docker build](#full-docker-build) workflow can be used so that you don't have to worry about installing all those tools on your local machine and instead only depend on having a working Docker install. @@ -197,7 +205,7 @@ Examples: ### Step 4: Commit -Create a commit with the proposed change changes: +Create a commit with the proposed changes: * Commit title and message (and PR title and description) must adhere to [conventionalcommits](https://www.conventionalcommits.org). * The title must begin with `feat(module): title`, `fix(module): title`, `refactor(module): title` or diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 94d3c3c05f46a..8b137891791fe 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -1,58 +1 @@ -incompatible-argument:@aws-cdk/aws-ecs.Ec2TaskDefinition. -incompatible-argument:@aws-cdk/aws-ecs.Ec2TaskDefinition.addVolume -incompatible-argument:@aws-cdk/aws-ecs.FargateTaskDefinition. -incompatible-argument:@aws-cdk/aws-ecs.FargateTaskDefinition.addVolume -incompatible-argument:@aws-cdk/aws-ecs.TaskDefinition. -incompatible-argument:@aws-cdk/aws-ecs.TaskDefinition.addVolume -change-return-type:@aws-cdk/core.Fn.getAtt -new-argument:@aws-cdk/aws-iam.ManagedPolicy. -new-argument:@aws-cdk/aws-iam.ManagedPolicy. -removed:@aws-cdk/aws-apigateway.AwsIntegration.props -removed:@aws-cdk/aws-apigateway.HttpIntegration.props -removed:@aws-cdk/aws-apigateway.Integration.props -removed:@aws-cdk/aws-apigateway.LambdaIntegration.props -removed:@aws-cdk/aws-apigateway.MockIntegration.props -removed:@aws-cdk/aws-ecs-patterns.ScheduledEc2TaskDefinitionOptions.schedule -removed:@aws-cdk/aws-ecs-patterns.ScheduledEc2TaskDefinitionOptions.cluster -removed:@aws-cdk/aws-ecs-patterns.ScheduledEc2TaskDefinitionOptions.desiredTaskCount -removed:@aws-cdk/aws-ecs-patterns.ScheduledEc2TaskDefinitionOptions.vpc -removed:@aws-cdk/aws-ecs-patterns.ScheduledFargateTaskDefinitionOptions.schedule -removed:@aws-cdk/aws-ecs-patterns.ScheduledFargateTaskDefinitionOptions.cluster -removed:@aws-cdk/aws-ecs-patterns.ScheduledFargateTaskDefinitionOptions.desiredTaskCount -removed:@aws-cdk/aws-ecs-patterns.ScheduledFargateTaskDefinitionOptions.vpc -incompatible-argument:@aws-cdk/aws-lambda.Function. -incompatible-argument:@aws-cdk/aws-lambda.SingletonFunction. -incompatible-argument:@aws-cdk/aws-lambda.Function.addEnvironment -changed-type:@aws-cdk/aws-dynamodb.Table.tableStreamArn -incompatible-argument:@aws-cdk/aws-apigateway.LambdaRestApi.addModel -incompatible-argument:@aws-cdk/aws-apigateway.Model. -incompatible-argument:@aws-cdk/aws-apigateway.RestApi.addModel -incompatible-argument:@aws-cdk/aws-apigateway.ProxyResource.addProxy -incompatible-argument:@aws-cdk/aws-apigateway.Resource.addProxy -incompatible-argument:@aws-cdk/aws-apigateway.ResourceBase.addProxy -incompatible-argument:@aws-cdk/aws-apigateway.IResource.addProxy -incompatible-argument:@aws-cdk/aws-apigateway.RequestAuthorizer. -incompatible-argument:@aws-cdk/aws-servicediscovery.Service.fromServiceAttributes -removed:@aws-cdk/core.ConstructNode.addReference -removed:@aws-cdk/core.ConstructNode.references -removed:@aws-cdk/core.OutgoingReference -change-return-type:@aws-cdk/aws-lambda-destinations.EventBridgeDestination.bind -change-return-type:@aws-cdk/aws-lambda-destinations.LambdaDestination.bind -change-return-type:@aws-cdk/aws-lambda-destinations.SnsDestination.bind -change-return-type:@aws-cdk/aws-lambda-destinations.SqsDestination.bind -removed:@aws-cdk/cdk-assets-schema.DockerImageDestination.imageUri -incompatible-argument:@aws-cdk/aws-iam.FederatedPrincipal. -incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addCondition -incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addConditions -incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addFederatedPrincipal -incompatible-argument:@aws-cdk/aws-iam.PrincipalPolicyFragment. -changed-type:@aws-cdk/aws-iam.FederatedPrincipal.conditions -changed-type:@aws-cdk/aws-iam.PrincipalPolicyFragment.conditions -changed-type:@aws-cdk/aws-iam.PrincipalWithConditions.conditions -removed:@aws-cdk/cdk-assets-schema.Placeholders -# Following two are because we're turning: properties: {string=>any} into a union of typed interfaces -# Needs to be removed after next release. -incompatible-argument:@aws-cdk/cloud-assembly-schema.Manifest.save -change-return-type:@aws-cdk/cloud-assembly-schema.Manifest.load -removed:@aws-cdk/core.DefaultStackSynthesizer.DEFAULT_DEPLOY_ACTION_ROLE_ARN -removed:@aws-cdk/core.DefaultStackSynthesizerProps.deployActionRoleArn + diff --git a/lerna.json b/lerna.json index baa3940d032fb..b533a6ac4d33c 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.41.0" + "version": "1.42.0" } diff --git a/packages/@aws-cdk/aws-backup/lib/resource.ts b/packages/@aws-cdk/aws-backup/lib/resource.ts index 5f3073642c05b..c0cd0fd2b8878 100644 --- a/packages/@aws-cdk/aws-backup/lib/resource.ts +++ b/packages/@aws-cdk/aws-backup/lib/resource.ts @@ -64,14 +64,14 @@ export class BackupResource { /** * A DynamoDB table */ - public static fromDynamoDbTable(table: dynamodb.Table) { + public static fromDynamoDbTable(table: dynamodb.ITable) { return BackupResource.fromArn(table.tableArn); } /** * An EC2 instance */ - public static fromEc2Instance(instance: ec2.Instance) { + public static fromEc2Instance(instance: ec2.IInstance) { return BackupResource.fromArn(Stack.of(instance).formatArn({ service: 'ec2', resource: 'instance', @@ -82,7 +82,7 @@ export class BackupResource { /** * An EFS file system */ - public static fromEfsFileSystem(fileSystem: efs.FileSystem) { + public static fromEfsFileSystem(fileSystem: efs.IFileSystem) { return BackupResource.fromArn(Stack.of(fileSystem).formatArn({ service: 'elasticfilesystem', resource: 'file-system', @@ -93,7 +93,7 @@ export class BackupResource { /** * A RDS database instance */ - public static fromRdsDatabaseInstance(instance: rds.DatabaseInstance) { + public static fromRdsDatabaseInstance(instance: rds.IDatabaseInstance) { return BackupResource.fromArn(instance.instanceArn); } diff --git a/packages/@aws-cdk/aws-backup/test/selection.test.ts b/packages/@aws-cdk/aws-backup/test/selection.test.ts index 4d8e7652a6925..75d1f6e6eade8 100644 --- a/packages/@aws-cdk/aws-backup/test/selection.test.ts +++ b/packages/@aws-cdk/aws-backup/test/selection.test.ts @@ -290,3 +290,44 @@ test('fromEc2Instance', () => { }, }); }); + +test('fromDynamoDbTable', () => { + // GIVEN + const newTable = new dynamodb.Table(stack, 'New', { + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + }); + const existingTable = dynamodb.Table.fromTableArn(stack, 'Existing', 'arn:aws:dynamodb:eu-west-1:123456789012:table/existing'); + + // WHEN + plan.addSelection('Selection', { + resources: [ + BackupResource.fromDynamoDbTable(newTable), + BackupResource.fromDynamoDbTable(existingTable), + ], + }); + + // THEN + expect(stack).toHaveResource('AWS::Backup::BackupSelection', { + BackupSelection: { + IamRoleArn: { + 'Fn::GetAtt': [ + 'PlanSelectionRole6D10F4B7', + 'Arn', + ], + }, + Resources: [ + { + 'Fn::GetAtt': [ + 'New8A81B073', + 'Arn', + ], + }, + 'arn:aws:dynamodb:eu-west-1:123456789012:table/existing', + ], + SelectionName: 'Selection', + }, + }); +}); diff --git a/packages/@aws-cdk/aws-cloudtrail/README.md b/packages/@aws-cdk/aws-cloudtrail/README.md index 11f2f93760ff9..a1619d4bf48fe 100644 --- a/packages/@aws-cdk/aws-cloudtrail/README.md +++ b/packages/@aws-cdk/aws-cloudtrail/README.md @@ -51,7 +51,9 @@ const trail = new cloudtrail.Trail(this, 'CloudTrail', { ``` This creates the same setup as above - but also logs events to a created CloudWatch Log stream. -By default, the created log group has a retention period of 365 Days, but this is also configurable. +By default, the created log group has a retention period of 365 Days, but this is also configurable +via the `cloudWatchLogsRetention` property. If you would like to specify the log group explicitly, +use the `cloudwatchLogGroup` property. For using CloudTrail event selector to log specific S3 events, you can use the `CloudTrailProps` configuration object. @@ -64,13 +66,12 @@ const trail = new cloudtrail.Trail(this, 'MyAmazingCloudTrail'); // Adds an event selector to the bucket magic-bucket. // By default, this includes management events and all operations (Read + Write) -trail.addS3EventSelector(["arn:aws:s3:::magic-bucket/"]); +trail.logAllS3DataEvents(); -// Adds an event selector to the bucket foo, with a specific configuration -trail.addS3EventSelector(["arn:aws:s3:::foo/"], { - includeManagementEvents: false, - readWriteType: ReadWriteType.ALL, -}); +// Adds an event selector to the bucket foo +trail.addS3EventSelector([{ + bucket: fooBucket // 'fooBucket' is of type s3.IBucket +}]); ``` For using CloudTrail event selector to log events about Lambda @@ -88,7 +89,7 @@ const lambdaFunction = new lambda.Function(stack, 'AnAmazingFunction', { }); // Add an event selector to log data events for all functions in the account. -trail.addLambdaEventSelector(["arn:aws:lambda"]); +trail.logAllLambdaDataEvents(); // Add an event selector to log data events for the provided Lambda functions. trail.addLambdaEventSelector([lambdaFunction.functionArn]); diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts index d12c7be68dd8b..9c38e6ca06814 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/cloudtrail.ts @@ -1,8 +1,10 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; +import * as sns from '@aws-cdk/aws-sns'; import { Construct, Resource, Stack } from '@aws-cdk/core'; import { CfnTrail } from './cloudtrail.generated'; @@ -63,23 +65,30 @@ export interface TrailProps { readonly sendToCloudWatchLogs?: boolean; /** - * How long to retain logs in CloudWatchLogs. Ignored if sendToCloudWatchLogs is false + * How long to retain logs in CloudWatchLogs. + * Ignored if sendToCloudWatchLogs is false or if cloudWatchLogGroup is set. * - * @default logs.RetentionDays.OneYear + * @default logs.RetentionDays.ONE_YEAR */ readonly cloudWatchLogsRetention?: logs.RetentionDays; + /** + * Log Group to which CloudTrail to push logs to. Ignored if sendToCloudWatchLogs is set to false. + * @default - a new log group is created and used. + */ + readonly cloudWatchLogGroup?: logs.ILogGroup; + /** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs. * * @default - No encryption. */ readonly kmsKey?: kms.IKey; - /** The name of an Amazon SNS topic that is notified when new log files are published. + /** SNS topic that is notified when new log files are published. * * @default - No notifications. */ - readonly snsTopic?: string; // TODO: fix to use L2 SNS + readonly snsTopic?: sns.ITopic; /** * The name of the trail. We recoomend customers do not set an explicit name. @@ -98,7 +107,7 @@ export interface TrailProps { * * @default - if not supplied a bucket will be created with all the correct permisions */ - readonly bucket?: s3.IBucket + readonly bucket?: s3.IBucket; } /** @@ -171,6 +180,12 @@ export class Trail extends Resource { */ public readonly trailSnsTopicArn: string; + /** + * The CloudWatch log group to which CloudTrail events are sent. + * `undefined` if `sendToCloudWatchLogs` property is false. + */ + public readonly logGroup?: logs.ILogGroup; + private s3bucket: s3.IBucket; private eventSelectors: EventSelector[] = []; @@ -200,19 +215,22 @@ export class Trail extends Resource { }, })); - let logGroup: logs.CfnLogGroup | undefined; let logsRole: iam.IRole | undefined; if (props.sendToCloudWatchLogs) { - logGroup = new logs.CfnLogGroup(this, 'LogGroup', { - retentionInDays: props.cloudWatchLogsRetention || logs.RetentionDays.ONE_YEAR, - }); + if (props.cloudWatchLogGroup) { + this.logGroup = props.cloudWatchLogGroup; + } else { + this.logGroup = new logs.LogGroup(this, 'LogGroup', { + retention: props.cloudWatchLogsRetention ?? logs.RetentionDays.ONE_YEAR, + }); + } logsRole = new iam.Role(this, 'LogsRole', { assumedBy: cloudTrailPrincipal }); logsRole.addToPolicy(new iam.PolicyStatement({ actions: ['logs:PutLogEvents', 'logs:CreateLogStream'], - resources: [logGroup.attrArn], + resources: [this.logGroup.logGroupArn], })); } @@ -234,9 +252,9 @@ export class Trail extends Resource { kmsKeyId: props.kmsKey && props.kmsKey.keyArn, s3BucketName: this.s3bucket.bucketName, s3KeyPrefix: props.s3KeyPrefix, - cloudWatchLogsLogGroupArn: logGroup && logGroup.attrArn, - cloudWatchLogsRoleArn: logsRole && logsRole.roleArn, - snsTopicName: props.snsTopic, + cloudWatchLogsLogGroupArn: this.logGroup?.logGroupArn, + cloudWatchLogsRoleArn: logsRole?.roleArn, + snsTopicName: props.snsTopic?.topicName, eventSelectors: this.eventSelectors, }); @@ -300,13 +318,24 @@ export class Trail extends Resource { * Data events: These events provide insight into the resource operations performed on or within a resource. * These are also known as data plane operations. * - * @param dataResourceValues the list of data resource ARNs to include in logging (maximum 250 entries). + * @param handlers the list of lambda function handlers whose data events should be logged (maximum 250 entries). * @param options the options to configure logging of management and data events. */ - public addLambdaEventSelector(dataResourceValues: string[], options: AddEventSelectorOptions = {}) { + public addLambdaEventSelector(handlers: lambda.IFunction[], options: AddEventSelectorOptions = {}) { + if (handlers.length === 0) { return; } + const dataResourceValues = handlers.map((h) => h.functionArn); return this.addEventSelector(DataResourceType.LAMBDA_FUNCTION, dataResourceValues, options); } + /** + * Log all Lamda data events for all lambda functions the account. + * @see https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html + * @default false + */ + public logAllLambdaDataEvents(options: AddEventSelectorOptions = {}) { + return this.addEventSelector(DataResourceType.LAMBDA_FUNCTION, [ 'arn:aws:lambda' ], options); + } + /** * When an event occurs in your account, CloudTrail evaluates whether the event matches the settings for your trails. * Only events that match your trail settings are delivered to your Amazon S3 bucket and Amazon CloudWatch Logs log group. @@ -316,13 +345,24 @@ export class Trail extends Resource { * Data events: These events provide insight into the resource operations performed on or within a resource. * These are also known as data plane operations. * - * @param dataResourceValues the list of data resource ARNs to include in logging (maximum 250 entries). + * @param s3Selector the list of S3 bucket with optional prefix to include in logging (maximum 250 entries). * @param options the options to configure logging of management and data events. */ - public addS3EventSelector(dataResourceValues: string[], options: AddEventSelectorOptions = {}) { + public addS3EventSelector(s3Selector: S3EventSelector[], options: AddEventSelectorOptions = {}) { + if (s3Selector.length === 0) { return; } + const dataResourceValues = s3Selector.map((sel) => `${sel.bucket.bucketArn}/${sel.objectPrefix ?? ''}`); return this.addEventSelector(DataResourceType.S3_OBJECT, dataResourceValues, options); } + /** + * Log all S3 data events for all objects for all buckets in the account. + * @see https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html + * @default false + */ + public logAllS3DataEvents(options: AddEventSelectorOptions = {}) { + return this.addEventSelector(DataResourceType.S3_OBJECT, [ 'arn:aws:s3:::' ], options); + } + /** * Create an event rule for when an event is recorded by any Trail in the account. * @@ -357,6 +397,20 @@ export interface AddEventSelectorOptions { readonly includeManagementEvents?: boolean; } +/** + * Selecting an S3 bucket and an optional prefix to be logged for data events. + */ +export interface S3EventSelector { + /** S3 bucket */ + readonly bucket: s3.IBucket; + + /** + * Data events for objects whose key matches this prefix will be logged. + * @default - all objects + */ + readonly objectPrefix?: string; +} + /** * Resource type for a data event */ diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index ff6eaa92cdf43..cc2107d9dabab 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -79,6 +79,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, @@ -90,6 +91,7 @@ "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", + "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.0.2" }, diff --git a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts index 019b0a9472626..7137a1ea4a7f0 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/cloudtrail.test.ts @@ -1,8 +1,8 @@ -import { SynthUtils } from '@aws-cdk/assert'; +import { ABSENT, SynthUtils } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { RetentionDays } from '@aws-cdk/aws-logs'; +import { LogGroup, RetentionDays } from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import { Stack } from '@aws-cdk/core'; import { ReadWriteType, Trail } from '../lib'; @@ -176,7 +176,7 @@ describe('cloudtrail', () => { Effect: 'Allow', Action: ['logs:PutLogEvents', 'logs:CreateLogStream'], Resource: { - 'Fn::GetAtt': ['MyAmazingCloudTrailLogGroupAAD65144', 'Arn'], + 'Fn::GetAtt': ['MyAmazingCloudTrailLogGroup2BE67F87', 'Arn'], }, }], }, @@ -205,57 +205,139 @@ describe('cloudtrail', () => { const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; expect(trail.DependsOn).toEqual([logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); }); + + test('enabled and with custom log group', () => { + const stack = getTestStack(); + const cloudWatchLogGroup = new LogGroup(stack, 'MyLogGroup', { + retention: RetentionDays.FIVE_DAYS, + }); + new Trail(stack, 'MyAmazingCloudTrail', { + sendToCloudWatchLogs: true, + cloudWatchLogsRetention: RetentionDays.ONE_WEEK, + cloudWatchLogGroup, + }); + + expect(stack).toHaveResource('AWS::Logs::LogGroup', { + RetentionInDays: 5, + }); + + expect(stack).toHaveResource('AWS::CloudTrail::Trail', { + CloudWatchLogsLogGroupArn: stack.resolve(cloudWatchLogGroup.logGroupArn), + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [{ + Resource: stack.resolve(cloudWatchLogGroup.logGroupArn), + }], + }, + }); + }); + + test('disabled', () => { + const stack = getTestStack(); + const t = new Trail(stack, 'MyAmazingCloudTrail', { + sendToCloudWatchLogs: false, + cloudWatchLogsRetention: RetentionDays.ONE_WEEK, + }); + expect(t.logGroup).toBeUndefined(); + expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); + }); }); describe('with event selectors', () => { - test('with default props', () => { + test('all s3 events', () => { const stack = getTestStack(); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); - cloudTrail.addS3EventSelector(['arn:aws:s3:::']); + cloudTrail.logAllS3DataEvents(); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); - expect(stack).not.toHaveResource('AWS::IAM::Role'); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [ 'arn:aws:s3:::' ], + }], + IncludeManagementEvents: ABSENT, + ReadWriteType: ABSENT, + }, + ], + }); + }); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; - expect(trail.Properties.EventSelectors.length).toEqual(1); - const selector = trail.Properties.EventSelectors[0]; - expect(selector.ReadWriteType).toBeUndefined(); - expect(selector.IncludeManagementEvents).toBeUndefined(); - expect(selector.DataResources.length).toEqual(1); - const dataResource = selector.DataResources[0]; - expect(dataResource.Type).toEqual('AWS::S3::Object'); - expect(dataResource.Values.length).toEqual(1); - expect(dataResource.Values[0]).toEqual('arn:aws:s3:::'); - expect(trail.DependsOn).toEqual(['MyAmazingCloudTrailS3Policy39C120B0']); + test('specific s3 buckets and objects', () => { + const stack = getTestStack(); + const bucket = new s3.Bucket(stack, 'testBucket', { bucketName: 'test-bucket' }); + + const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); + cloudTrail.addS3EventSelector([{ bucket }]); + cloudTrail.addS3EventSelector([{ + bucket, + objectPrefix: 'prefix-1/prefix-2', + }]); + + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [{ + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': [ 'testBucketDF4D7D1A', 'Arn' ]}, + '/', + ], + ], + }], + }], + }, + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [{ + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': [ 'testBucketDF4D7D1A', 'Arn' ]}, + '/prefix-1/prefix-2', + ], + ], + }], + }], + }, + ], + }); + }); + + test('no s3 event selector when list is empty', () => { + const stack = getTestStack(); + const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); + cloudTrail.addS3EventSelector([]); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [], + }); }); test('with hand-specified props', () => { const stack = getTestStack(); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); - cloudTrail.addS3EventSelector(['arn:aws:s3:::'], { includeManagementEvents: false, readWriteType: ReadWriteType.READ_ONLY }); + cloudTrail.logAllS3DataEvents({ includeManagementEvents: false, readWriteType: ReadWriteType.READ_ONLY }); - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::S3::Bucket'); - expect(stack).toHaveResource('AWS::S3::BucketPolicy', ExpectedBucketPolicyProperties); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); - expect(stack).not.toHaveResource('AWS::IAM::Role'); - - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; - expect(trail.Properties.EventSelectors.length).toEqual(1); - const selector = trail.Properties.EventSelectors[0]; - expect(selector.ReadWriteType).toEqual('ReadOnly'); - expect(selector.IncludeManagementEvents).toEqual(false); - expect(selector.DataResources.length).toEqual(1); - const dataResource = selector.DataResources[0]; - expect(dataResource.Type).toEqual('AWS::S3::Object'); - expect(dataResource.Values.length).toEqual(1); - expect(dataResource.Values[0]).toEqual('arn:aws:s3:::'); - expect(trail.DependsOn).toEqual(['MyAmazingCloudTrailS3Policy39C120B0']); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::S3::Object', + Values: [ 'arn:aws:s3:::' ], + }], + IncludeManagementEvents: false, + ReadWriteType: 'ReadOnly', + }, + ], + }); }); test('with management event', () => { @@ -263,12 +345,14 @@ describe('cloudtrail', () => { new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WRITE_ONLY }); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; - expect(trail.Properties.EventSelectors.length).toEqual(1); - const selector = trail.Properties.EventSelectors[0]; - expect(selector.ReadWriteType).toEqual('WriteOnly'); - expect(selector.IncludeManagementEvents).toEqual(true); - expect(selector.DataResources).toEqual(undefined); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + IncludeManagementEvents: true, + ReadWriteType: 'WriteOnly', + }, + ], + }); }); test('for Lambda function data event', () => { @@ -280,46 +364,38 @@ describe('cloudtrail', () => { }); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); - cloudTrail.addLambdaEventSelector([lambdaFunction.functionArn]); - - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).toHaveResource('AWS::Lambda::Function'); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); + cloudTrail.addLambdaEventSelector([lambdaFunction]); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; - expect(trail.Properties.EventSelectors.length).toEqual(1); - const selector = trail.Properties.EventSelectors[0]; - expect(selector.ReadWriteType).toBeUndefined(); - expect(selector.IncludeManagementEvents).toBeUndefined(); - expect(selector.DataResources.length).toEqual(1); - const dataResource = selector.DataResources[0]; - expect(dataResource.Type).toEqual('AWS::Lambda::Function'); - expect(dataResource.Values.length).toEqual(1); - expect(dataResource.Values[0]).toEqual({ 'Fn::GetAtt': [ 'LambdaFunctionBF21E41F', 'Arn' ] }); - expect(trail.DependsOn).toEqual(['MyAmazingCloudTrailS3Policy39C120B0']); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::Lambda::Function', + Values: [{ + 'Fn::GetAtt': [ 'LambdaFunctionBF21E41F', 'Arn' ], + }], + }], + }, + ], + }); }); test('for all Lambda function data events', () => { const stack = getTestStack(); const cloudTrail = new Trail(stack, 'MyAmazingCloudTrail'); - cloudTrail.addLambdaEventSelector(['arn:aws:lambda']); - - expect(stack).toHaveResource('AWS::CloudTrail::Trail'); - expect(stack).not.toHaveResource('AWS::Logs::LogGroup'); - expect(stack).not.toHaveResource('AWS::IAM::Role'); + cloudTrail.logAllLambdaDataEvents(); - const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; - expect(trail.Properties.EventSelectors.length).toEqual(1); - const selector = trail.Properties.EventSelectors[0]; - expect(selector.ReadWriteType).toBeUndefined(); - expect(selector.IncludeManagementEvents).toBeUndefined(); - expect(selector.DataResources.length).toEqual(1); - const dataResource = selector.DataResources[0]; - expect(dataResource.Type).toEqual('AWS::Lambda::Function'); - expect(dataResource.Values.length).toEqual(1); - expect(dataResource.Values[0]).toEqual('arn:aws:lambda'); - expect(trail.DependsOn).toEqual(['MyAmazingCloudTrailS3Policy39C120B0']); + expect(stack).toHaveResourceLike('AWS::CloudTrail::Trail', { + EventSelectors: [ + { + DataResources: [{ + Type: 'AWS::Lambda::Function', + Values: [ 'arn:aws:lambda' ], + }], + }, + ], + }); }); }); }); diff --git a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts index fa57b1f2caf05..ad8614b3c1564 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail-supplied-bucket.lit.ts @@ -37,7 +37,7 @@ Trailbucket.addToResourcePolicy(new iam.PolicyStatement({ const trail = new cloudtrail.Trail(stack, 'Trail', {bucket: Trailbucket}); -trail.addLambdaEventSelector([lambdaFunction.functionArn]); -trail.addS3EventSelector([bucket.arnForObjects('')]); +trail.addLambdaEventSelector([lambdaFunction]); +trail.addS3EventSelector([{bucket}]); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.ts b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.ts index bee7fc432d6ed..5f53f4efeb0fa 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/integ.cloudtrail.lit.ts @@ -14,7 +14,7 @@ const lambdaFunction = new lambda.Function(stack, 'LambdaFunction', { }); const trail = new cloudtrail.Trail(stack, 'Trail'); -trail.addLambdaEventSelector([lambdaFunction.functionArn]); -trail.addS3EventSelector([bucket.arnForObjects('')]); +trail.addLambdaEventSelector([lambdaFunction]); +trail.addS3EventSelector([{bucket}]); app.synth(); diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/appscaling.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/appscaling.ts index bcb4b2ed1c581..2241796f47d2b 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/lib/appscaling.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/appscaling.ts @@ -9,6 +9,10 @@ export class ApplicationScalingAction implements cloudwatch.IAlarmAction { constructor(private readonly stepScalingAction: appscaling.StepScalingAction) { } + /** + * Returns an alarm action configuration to use an ApplicationScaling StepScalingAction + * as an alarm action + */ public bind(_scope: cdk.Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { return { alarmActionArn: this.stepScalingAction.scalingPolicyArn }; } diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/autoscaling.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/autoscaling.ts index 577f56bd47fd5..5ec6e62fe246c 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/lib/autoscaling.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/autoscaling.ts @@ -9,6 +9,10 @@ export class AutoScalingAction implements cloudwatch.IAlarmAction { constructor(private readonly stepScalingAction: autoscaling.StepScalingAction) { } + /** + * Returns an alarm action configuration to use an AutoScaling StepScalingAction + * as an alarm action + */ public bind(_scope: cdk.Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { return { alarmActionArn: this.stepScalingAction.scalingPolicyArn }; } diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/lib/sns.ts b/packages/@aws-cdk/aws-cloudwatch-actions/lib/sns.ts index 0067cf4518c28..deb882be507b3 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/lib/sns.ts +++ b/packages/@aws-cdk/aws-cloudwatch-actions/lib/sns.ts @@ -9,6 +9,9 @@ export class SnsAction implements cloudwatch.IAlarmAction { constructor(private readonly topic: sns.ITopic) { } + /** + * Returns an alarm action configuration to use an SNS topic as an alarm action + */ public bind(_scope: Construct, _alarm: cloudwatch.IAlarm): cloudwatch.AlarmActionConfig { return { alarmActionArn: this.topic.topicArn }; } diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/package.json b/packages/@aws-cdk/aws-cloudwatch-actions/package.json index eefec8c4a581d..8f8d1edd98766 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/package.json +++ b/packages/@aws-cdk/aws-cloudwatch-actions/package.json @@ -90,13 +90,6 @@ "node": ">= 10.13.0 <13 || >=13.7.0" }, "stability": "stable", - "awslint": { - "exclude": [ - "docs-public-apis:@aws-cdk/aws-cloudwatch-actions.ApplicationScalingAction.bind", - "docs-public-apis:@aws-cdk/aws-cloudwatch-actions.AutoScalingAction.bind", - "docs-public-apis:@aws-cdk/aws-cloudwatch-actions.SnsAction.bind" - ] - }, "awscdkio": { "announce": false }, diff --git a/packages/@aws-cdk/aws-codeguruprofiler/README.md b/packages/@aws-cdk/aws-codeguruprofiler/README.md index 23b5ff77af24f..5fcb3137d296b 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/README.md +++ b/packages/@aws-cdk/aws-codeguruprofiler/README.md @@ -9,8 +9,26 @@ --- -This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. +Amazon CodeGuru Profiler collects runtime performance data from your live applications, and provides recommendations that can help you fine-tune your application performance. + +### Installation + +Import to your project: ```ts import * as codeguruprofiler from '@aws-cdk/aws-codeguruprofiler'; ``` + +### Basic usage + +Here's how to setup a profiling group and give your compute role permissions to publish to the profiling group to the profiling agent can publish profiling information: + +```ts +// The execution role of your application that publishes to the ProfilingGroup via CodeGuru Profiler Profiling Agent. (the following is merely an example) +const publishAppRole = new Role(stack, 'PublishAppRole', { + assumedBy: new AccountRootPrincipal(), +}); + +const profilingGroup = new ProfilingGroup(stack, 'MyProfilingGroup'); +profilingGroup.grantPublish(publishAppRole); +``` diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/index.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/index.ts index 1dca345aee39a..6ee79ba3c2171 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/index.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/index.ts @@ -1,2 +1,3 @@ // AWS::CodeGuruProfiler CloudFormation Resources: export * from './codeguruprofiler.generated'; +export * from './profiling-group'; diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts new file mode 100644 index 0000000000000..f4d356e093204 --- /dev/null +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -0,0 +1,180 @@ +import { Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { Construct, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { CfnProfilingGroup } from './codeguruprofiler.generated'; + +/** + * IResource represents a Profiling Group. + */ +export interface IProfilingGroup extends IResource { + + /** + * A name for the profiling group. + * + * @attribute + */ + readonly profilingGroupName: string; + + /** + * Grant access to publish profiling information to the Profiling Group to the given identity. + * + * This will grant the following permissions: + * + * - codeguru-profiler:ConfigureAgent + * - codeguru-profiler:PostAgentProfile + * + * @param grantee Principal to grant publish rights to + */ + grantPublish(grantee: IGrantable): Grant; + + /** + * Grant access to read profiling information from the Profiling Group to the given identity. + * + * This will grant the following permissions: + * + * - codeguru-profiler:GetProfile + * - codeguru-profiler:DescribeProfilingGroup + * + * @param grantee Principal to grant read rights to + */ + grantRead(grantee: IGrantable): Grant; + +} + +abstract class ProfilingGroupBase extends Resource implements IProfilingGroup { + + public abstract readonly profilingGroupName: string; + + public abstract readonly profilingGroupArn: string; + + /** + * Grant access to publish profiling information to the Profiling Group to the given identity. + * + * This will grant the following permissions: + * + * - codeguru-profiler:ConfigureAgent + * - codeguru-profiler:PostAgentProfile + * + * @param grantee Principal to grant publish rights to + */ + public grantPublish(grantee: IGrantable) { + // https://docs.aws.amazon.com/codeguru/latest/profiler-ug/security-iam.html#security-iam-access-control + return Grant.addToPrincipal({ + grantee, + actions: ['codeguru-profiler:ConfigureAgent', 'codeguru-profiler:PostAgentProfile'], + resourceArns: [this.profilingGroupArn], + }); + } + + /** + * Grant access to read profiling information from the Profiling Group to the given identity. + * + * This will grant the following permissions: + * + * - codeguru-profiler:GetProfile + * - codeguru-profiler:DescribeProfilingGroup + * + * @param grantee Principal to grant read rights to + */ + public grantRead(grantee: IGrantable) { + // https://docs.aws.amazon.com/codeguru/latest/profiler-ug/security-iam.html#security-iam-access-control + return Grant.addToPrincipal({ + grantee, + actions: ['codeguru-profiler:GetProfile', 'codeguru-profiler:DescribeProfilingGroup'], + resourceArns: [this.profilingGroupArn], + }); + } + +} + +/** + * Properties for creating a new Profiling Group. + */ +export interface ProfilingGroupProps { + + /** + * A name for the profiling group. + * @default - automatically generated name. + */ + readonly profilingGroupName?: string; + +} + +/** + * A new Profiling Group. + */ +export class ProfilingGroup extends ProfilingGroupBase { + + /** + * Import an existing Profiling Group provided a Profiling Group Name. + * + * @param scope The parent creating construct + * @param id The construct's name + * @param profilingGroupName Profiling Group Name + */ + public static fromProfilingGroupName(scope: Construct, id: string, profilingGroupName: string): IProfilingGroup { + const stack = Stack.of(scope); + + return this.fromProfilingGroupArn(scope, id, stack.formatArn({ + service: 'codeguru-profiler', + resource: 'profilingGroup', + resourceName: profilingGroupName, + })); + } + + /** + * Import an existing Profiling Group provided an ARN. + * + * @param scope The parent creating construct + * @param id The construct's name + * @param profilingGroupArn Profiling Group ARN + */ + public static fromProfilingGroupArn(scope: Construct, id: string, profilingGroupArn: string): IProfilingGroup { + class Import extends ProfilingGroupBase { + public readonly profilingGroupName = Stack.of(scope).parseArn(profilingGroupArn).resource; + public readonly profilingGroupArn = profilingGroupArn; + } + + return new Import(scope, id); + } + + /** + * The name of the Profiling Group. + * + * @attribute + */ + public readonly profilingGroupName: string; + + /** + * The ARN of the Profiling Group. + * + * @attribute + */ + public readonly profilingGroupArn: string; + + constructor(scope: Construct, id: string, props: ProfilingGroupProps = {}) { + super(scope, id, { + physicalName: props.profilingGroupName ?? Lazy.stringValue({ produce: () => this.generateUniqueId() }), + }); + + const profilingGroup = new CfnProfilingGroup(this, 'ProfilingGroup', { + profilingGroupName: this.physicalName, + }); + + this.profilingGroupName = this.getResourceNameAttribute(profilingGroup.ref); + + this.profilingGroupArn = this.getResourceArnAttribute(profilingGroup.attrArn, { + service: 'codeguru-profiler', + resource: 'profilingGroup', + resourceName: this.physicalName, + }); + } + + private generateUniqueId(): string { + const name = this.node.uniqueId; + if (name.length > 240) { + return name.substring(0, 120) + name.substring(name.length - 120); + } + return name; + } + +} diff --git a/packages/@aws-cdk/aws-codeguruprofiler/package.json b/packages/@aws-cdk/aws-codeguruprofiler/package.json index 71efbaf81a1d0..a114721c9e514 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/package.json +++ b/packages/@aws-cdk/aws-codeguruprofiler/package.json @@ -67,14 +67,18 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "pkglint": "0.0.0" }, "dependencies": { + "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/core": "0.0.0" }, "peerDependencies": { - "@aws-cdk/core": "0.0.0" + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-iam": "0.0.0", + "constructs": "^3.0.2" }, "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.expected.json b/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.expected.json new file mode 100644 index 0000000000000..8ea1221f6bbe8 --- /dev/null +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.expected.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "MyProfilingGroup829F0507": { + "Type": "AWS::CodeGuruProfiler::ProfilingGroup", + "Properties": { + "ProfilingGroupName": "ProfilerGroupIntegrationTestMyProfilingGroup81DA69A3" + } + }, + "PublishAppRole9FEBD682": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PublishAppRoleDefaultPolicyCA1E15C3": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codeguru-profiler:ConfigureAgent", + "codeguru-profiler:PostAgentProfile" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyProfilingGroup829F0507", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PublishAppRoleDefaultPolicyCA1E15C3", + "Roles": [ + { + "Ref": "PublishAppRole9FEBD682" + } + ] + } + }, + "ReadAppRole52FE6317": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ReadAppRoleDefaultPolicy4BB8955C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codeguru-profiler:GetProfile", + "codeguru-profiler:DescribeProfilingGroup" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyProfilingGroup829F0507", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ReadAppRoleDefaultPolicy4BB8955C", + "Roles": [ + { + "Ref": "ReadAppRole52FE6317" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.ts new file mode 100644 index 0000000000000..d947e85e823a4 --- /dev/null +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/integ.profiler-group.ts @@ -0,0 +1,28 @@ +import { AccountRootPrincipal, Role } from '@aws-cdk/aws-iam'; +import { App, Stack, StackProps } from '@aws-cdk/core'; +import { ProfilingGroup } from '../lib'; + +class ProfilerGroupIntegrationTest extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); + + const profilingGroup = new ProfilingGroup(this, 'MyProfilingGroup'); + + const publishAppRole = new Role(this, 'PublishAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + profilingGroup.grantPublish(publishAppRole); + + const readAppRole = new Role(this, 'ReadAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + profilingGroup.grantRead(readAppRole); + + } +} + +const app = new App(); + +new ProfilerGroupIntegrationTest(app, 'ProfilerGroupIntegrationTest'); + +app.synth(); diff --git a/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts new file mode 100644 index 0000000000000..0fbf063cccfaa --- /dev/null +++ b/packages/@aws-cdk/aws-codeguruprofiler/test/profiling-group.test.ts @@ -0,0 +1,393 @@ +import { expect } from '@aws-cdk/assert'; +import { AccountRootPrincipal, Role } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/core'; +import { ProfilingGroup } from '../lib'; + +// tslint:disable:object-literal-key-quotes + +describe('profiling group', () => { + + test('attach read permission to Profiling group via fromProfilingGroupArn', () => { + const stack = new Stack(); + // dummy role to test out read permissions on ProfilingGroup + const readAppRole = new Role(stack, 'ReadAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + + const profilingGroup = ProfilingGroup.fromProfilingGroupArn(stack, 'MyProfilingGroup', 'arn:aws:codeguru-profiler:us-east-1:1234567890:profilingGroup/MyAwesomeProfilingGroup'); + profilingGroup.grantRead(readAppRole); + + expect(stack).toMatch({ + 'Resources': { + 'ReadAppRole52FE6317': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::', + { + 'Ref': 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + }, + ], + 'Version': '2012-10-17', + }, + }, + }, + 'ReadAppRoleDefaultPolicy4BB8955C': { + 'Type': 'AWS::IAM::Policy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 'codeguru-profiler:GetProfile', + 'codeguru-profiler:DescribeProfilingGroup', + ], + 'Effect': 'Allow', + 'Resource': 'arn:aws:codeguru-profiler:us-east-1:1234567890:profilingGroup/MyAwesomeProfilingGroup', + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'ReadAppRoleDefaultPolicy4BB8955C', + 'Roles': [ + { + 'Ref': 'ReadAppRole52FE6317', + }, + ], + }, + }, + }, + }); + }); + + test('attach publish permission to Profiling group via fromProfilingGroupName', () => { + const stack = new Stack(); + // dummy role to test out publish permissions on ProfilingGroup + const publishAppRole = new Role(stack, 'PublishAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + + const profilingGroup = ProfilingGroup.fromProfilingGroupName(stack, 'MyProfilingGroup', 'MyAwesomeProfilingGroup'); + profilingGroup.grantPublish(publishAppRole); + + expect(stack).toMatch({ + 'Resources': { + 'PublishAppRole9FEBD682': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::', + { + 'Ref': 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + }, + ], + 'Version': '2012-10-17', + }, + }, + }, + 'PublishAppRoleDefaultPolicyCA1E15C3': { + 'Type': 'AWS::IAM::Policy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + 'Effect': 'Allow', + 'Resource': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':codeguru-profiler:', + { + 'Ref': 'AWS::Region', + }, + ':', + { + 'Ref': 'AWS::AccountId', + }, + ':profilingGroup/MyAwesomeProfilingGroup', + ], + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'PublishAppRoleDefaultPolicyCA1E15C3', + 'Roles': [ + { + 'Ref': 'PublishAppRole9FEBD682', + }, + ], + }, + }, + }, + }); + }); + + test('default profiling group', () => { + const stack = new Stack(); + new ProfilingGroup(stack, 'MyProfilingGroup', { + profilingGroupName: 'MyAwesomeProfilingGroup', + }); + + expect(stack).toMatch({ + 'Resources': { + 'MyProfilingGroup829F0507': { + 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', + 'Properties': { + 'ProfilingGroupName': 'MyAwesomeProfilingGroup', + }, + }, + }, + }); + }); + + test('default profiling group without name', () => { + const stack = new Stack(); + new ProfilingGroup(stack, 'MyProfilingGroup', { + }); + + expect(stack).toMatch({ + 'Resources': { + 'MyProfilingGroup829F0507': { + 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', + 'Properties': { + 'ProfilingGroupName': 'MyProfilingGroup', + }, + }, + }, + }); + }); + + test('default profiling group without name when name exceeding limit is generated', () => { + const stack = new Stack(); + new ProfilingGroup(stack, 'MyProfilingGroupWithAReallyLongProfilingGroupNameThatExceedsTheLimitOfProfilingGroupNameSize_InOrderToDoSoTheNameMustBeGreaterThanTwoHundredAndFiftyFiveCharacters_InSuchCasesWePickUpTheFirstOneTwentyCharactersFromTheBeginningAndTheEndAndConcatenateThemToGetTheIdentifier', { + }); + + expect(stack).toMatch({ + 'Resources': { + 'MyProfilingGroupWithAReallyLongProfilingGroupNameThatExceedsTheLimitOfProfilingGroupNameSizeInOrderToDoSoTheNameMustBeGreaterThanTwoHundredAndFiftyFiveCharactersInSuchCasesWePickUpTheFirstOneTwentyCharactersFromTheBeginningAndTheEndAndConca4B39908C': { + 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', + 'Properties': { + 'ProfilingGroupName': 'MyProfilingGroupWithAReallyLongProfilingGroupNameThatExceedsTheLimitOfProfilingGroupNameSizeInOrderToDoSoTheNameMustBeGrnTwoHundredAndFiftyFiveCharactersInSuchCasesWePickUpTheFirstOneTwentyCharactersFromTheBeginningAndTheEndAndConca2FE009B0', + }, + }, + }, + }); + }); + + test('grant publish permissions profiling group', () => { + const stack = new Stack(); + const profilingGroup = new ProfilingGroup(stack, 'MyProfilingGroup', { + profilingGroupName: 'MyAwesomeProfilingGroup', + }); + const publishAppRole = new Role(stack, 'PublishAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + + profilingGroup.grantPublish(publishAppRole); + + expect(stack).toMatch({ + 'Resources': { + 'MyProfilingGroup829F0507': { + 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', + 'Properties': { + 'ProfilingGroupName': 'MyAwesomeProfilingGroup', + }, + }, + 'PublishAppRole9FEBD682': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::', + { + 'Ref': 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + }, + ], + 'Version': '2012-10-17', + }, + }, + }, + 'PublishAppRoleDefaultPolicyCA1E15C3': { + 'Type': 'AWS::IAM::Policy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 'codeguru-profiler:ConfigureAgent', + 'codeguru-profiler:PostAgentProfile', + ], + 'Effect': 'Allow', + 'Resource': { + 'Fn::GetAtt': [ + 'MyProfilingGroup829F0507', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'PublishAppRoleDefaultPolicyCA1E15C3', + 'Roles': [ + { + 'Ref': 'PublishAppRole9FEBD682', + }, + ], + }, + }, + }, + }); + }); + + test('grant read permissions profiling group', () => { + const stack = new Stack(); + const profilingGroup = new ProfilingGroup(stack, 'MyProfilingGroup', { + profilingGroupName: 'MyAwesomeProfilingGroup', + }); + const readAppRole = new Role(stack, 'ReadAppRole', { + assumedBy: new AccountRootPrincipal(), + }); + + profilingGroup.grantRead(readAppRole); + + expect(stack).toMatch({ + 'Resources': { + 'MyProfilingGroup829F0507': { + 'Type': 'AWS::CodeGuruProfiler::ProfilingGroup', + 'Properties': { + 'ProfilingGroupName': 'MyAwesomeProfilingGroup', + }, + }, + 'ReadAppRole52FE6317': { + 'Type': 'AWS::IAM::Role', + 'Properties': { + 'AssumeRolePolicyDocument': { + 'Statement': [ + { + 'Action': 'sts:AssumeRole', + 'Effect': 'Allow', + 'Principal': { + 'AWS': { + 'Fn::Join': [ + '', + [ + 'arn:', + { + 'Ref': 'AWS::Partition', + }, + ':iam::', + { + 'Ref': 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + }, + ], + 'Version': '2012-10-17', + }, + }, + }, + 'ReadAppRoleDefaultPolicy4BB8955C': { + 'Type': 'AWS::IAM::Policy', + 'Properties': { + 'PolicyDocument': { + 'Statement': [ + { + 'Action': [ + 'codeguru-profiler:GetProfile', + 'codeguru-profiler:DescribeProfilingGroup', + ], + 'Effect': 'Allow', + 'Resource': { + 'Fn::GetAtt': [ + 'MyProfilingGroup829F0507', + 'Arn', + ], + }, + }, + ], + 'Version': '2012-10-17', + }, + 'PolicyName': 'ReadAppRoleDefaultPolicy4BB8955C', + 'Roles': [ + { + 'Ref': 'ReadAppRole52FE6317', + }, + ], + }, + }, + }, + }); + }); + +}); 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 ca18d03b47eef..e9ad9cc11141d 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.WRITE_ONLY, includeManagementEvents: false }); +trail.addS3EventSelector([ { bucket, objectPrefix: 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/lib/cross-region-support-stack.ts b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts index 47227f4fb689d..00d0c5ca29493 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-support-stack.ts @@ -71,6 +71,8 @@ export interface CrossRegionSupportStackProps { * @example '012345678901' */ readonly account: string; + + readonly synthesizer: cdk.IStackSynthesizer | undefined; } /** @@ -90,6 +92,7 @@ export class CrossRegionSupportStack extends cdk.Stack { region: props.region, account: props.account, }, + synthesizer: props.synthesizer, }); const crossRegionSupportConstruct = new CrossRegionSupportConstruct(this, 'Default'); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 05b4c174f6aa6..26c86887e98bc 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -2,7 +2,10 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; -import { App, Construct, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/core'; +import { + App, BootstraplessSynthesizer, Construct, DefaultStackSynthesizer, + IStackSynthesizer, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token, +} from '@aws-cdk/core'; import { ActionCategory, IAction, IPipeline, IStage } from './action'; import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionSupportConstruct, CrossRegionSupportStack } from './cross-region-support-stack'; @@ -483,6 +486,7 @@ export class Pipeline extends PipelineBase { pipelineStackName: pipelineStack.stackName, region: actionRegion, account: pipelineAccount, + synthesizer: this.getCrossRegionSupportSynthesizer(), }); } @@ -492,6 +496,23 @@ export class Pipeline extends PipelineBase { }; } + private getCrossRegionSupportSynthesizer(): IStackSynthesizer | undefined { + if (this.stack.synthesizer instanceof DefaultStackSynthesizer) { + // if we have the new synthesizer, + // we need a bootstrapless copy of it, + // because we don't want to require bootstrapping the environment + // of the pipeline account in this replication region + return new BootstraplessSynthesizer({ + deployRoleArn: this.stack.synthesizer.deployRoleArn, + cloudFormationExecutionRoleArn: this.stack.synthesizer.cloudFormationExecutionRoleArn, + }); + } else { + // any other synthesizer: just return undefined + // (ie., use the default based on the context settings) + return undefined; + } + } + private generateNameForDefaultBucketKeyAlias(): string { const prefix = 'alias/codepipeline-'; const maxAliasLength = 256; diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 36154be191da0..0a94e85b6a724 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -68,6 +68,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", + "@aws-cdk/cx-api": "0.0.0", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index 153e24d882f8a..5d1c91edd51af 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -3,6 +3,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; import * as codepipeline from '../lib'; import { FakeBuildAction } from './fake-build-action'; @@ -46,7 +47,7 @@ export = { }, 'that is cross-region': { - 'validates that source actions are in the same account as the pipeline'(test: Test) { + 'validates that source actions are in the same region as the pipeline'(test: Test) { const app = new cdk.App(); const stack = new cdk.Stack(app, 'PipelineStack', { env: { region: 'us-west-1', account: '123456789012' }}); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); @@ -296,6 +297,46 @@ export = { test.done(); }, + + 'generates the support stack containing the replication Bucket without the need to bootstrap in that environment'(test: Test) { + const app = new cdk.App({ + treeMetadata: false, // we can't set the context otherwise, because App will have a child + }); + app.node.setContext(cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT, true); + + const pipelineStack = new cdk.Stack(app, 'PipelineStack', { + env: { region: 'us-west-2', account: '123456789012' }, + }); + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [new FakeSourceAction({ + actionName: 'Source', + output: sourceOutput, + })], + }, + { + stageName: 'Build', + actions: [new FakeBuildAction({ + actionName: 'Build', + input: sourceOutput, + region: 'eu-south-1', + })], + }, + ], + }); + + const assembly = app.synth(); + const supportStackArtifact = assembly.getStackByName('PipelineStack-support-eu-south-1'); + test.equal(supportStackArtifact.assumeRoleArn, + 'arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-deploy-role-123456789012-us-west-2'); + test.equal(supportStackArtifact.cloudFormationExecutionRoleArn, + 'arn:${AWS::Partition}:iam::123456789012:role/cdk-hnb659fds-cfn-exec-role-123456789012-us-west-2'); + + test.done(); + }, }, 'that is cross-account': { diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts index db7fcaa8e163e..e2a76c64120ef 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts @@ -17,7 +17,7 @@ export interface RequiredAttributes { readonly birthdate?: boolean; /** - * Whether theb user's e-mail address, represented as an RFC 5322 [RFC5322] addr-spec, is a required attribute. + * Whether the user's e-mail address, represented as an RFC 5322 [RFC5322] addr-spec, is a required attribute. * @default false */ readonly email?: boolean; diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 186db2266c2fa..f63ff13a25cf4 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -1114,9 +1114,9 @@ export class Table extends TableBase { * @param nonKeyAttributes a list of non-key attribute names */ private validateNonKeyAttributes(nonKeyAttributes: string[]) { - if (this.nonKeyAttributes.size + nonKeyAttributes.length > 20) { + if (this.nonKeyAttributes.size + nonKeyAttributes.length > 100) { // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html#limits-secondary-indexes - throw new RangeError('a maximum number of nonKeyAttributes across all of secondary indexes is 20'); + throw new RangeError('a maximum number of nonKeyAttributes across all of secondary indexes is 100'); } // store all non-key attributes diff --git a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts index c422330e2c1ce..068cfaf5b0edb 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts +++ b/packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts @@ -1114,7 +1114,7 @@ test('error when adding a global secondary index with projection type INCLUDE, b const table = new Table(stack, CONSTRUCT_NAME, { partitionKey: TABLE_PARTITION_KEY, sortKey: TABLE_SORT_KEY }); const gsiNonKeyAttributeGenerator = NON_KEY_ATTRIBUTE_GENERATOR(GSI_NON_KEY); const gsiNonKeyAttributes: string[] = []; - for (let i = 0; i < 21; i++) { + for (let i = 0; i < 101; i++) { gsiNonKeyAttributes.push(gsiNonKeyAttributeGenerator.next().value); } @@ -1124,7 +1124,7 @@ test('error when adding a global secondary index with projection type INCLUDE, b sortKey: GSI_SORT_KEY, projectionType: ProjectionType.INCLUDE, nonKeyAttributes: gsiNonKeyAttributes, - })).toThrow(/a maximum number of nonKeyAttributes across all of secondary indexes is 20/); + })).toThrow(/a maximum number of nonKeyAttributes across all of secondary indexes is 100/); }); test('error when adding a global secondary index with read or write capacity on a PAY_PER_REQUEST table', () => { diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 4f9103c9504fa..d77259e7fb3fb 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -526,29 +526,34 @@ With services account you can provide Kubernetes Pods access to AWS resources. ```ts // add service account -const serviceAccount = cluster.addServiceAccount('MyServiceAccount'); +const sa = cluster.addServiceAccount('MyServiceAccount'); const bucket = new Bucket(this, 'Bucket'); bucket.grantReadWrite(serviceAccount); -cluster.addResource('mypod', { +const mypod = cluster.addResource('mypod', { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'mypod' }, spec: { + serviceAccountName: sa.serviceAccountName containers: [ { name: 'hello', image: 'paulbouwer/hello-kubernetes:1.5', ports: [ { containerPort: 8080 } ], - serviceAccountName: serviceAccount.serviceAccountName + } ] } }); -``` -> Warning: Currently there are no condition set on the IAM Role which results that there are no restrictions on other pods to assume the role. This will be improved in the near future. +// create the resource after the service account +mypod.node.addDependency(sa); + +// print the IAM role arn for this service account +new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: sa.role.roleArn }) +``` ### Roadmap diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts index 47449325d4304..8733463cce31b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/cluster.ts @@ -199,6 +199,7 @@ export class ClusterResourceHandler extends ResourceHandler { Arn: cluster.arn, CertificateAuthorityData: cluster.certificateAuthority?.data, OpenIdConnectIssuerUrl: cluster.identity?.oidc?.issuer, + OpenIdConnectIssuer: cluster.identity?.oidc?.issuer?.substring(8), // Strips off https:// from the issuer url }, }; } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index e4c8f13917e8c..52557776c97e8 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -19,6 +19,7 @@ export class ClusterResource extends Construct { public readonly attrArn: string; public readonly attrCertificateAuthorityData: string; public readonly attrOpenIdConnectIssuerUrl: string; + public readonly attrOpenIdConnectIssuer: string; public readonly ref: string; /** @@ -126,6 +127,7 @@ export class ClusterResource extends Construct { this.attrArn = Token.asString(resource.getAtt('Arn')); this.attrCertificateAuthorityData = Token.asString(resource.getAtt('CertificateAuthorityData')); this.attrOpenIdConnectIssuerUrl = Token.asString(resource.getAtt('OpenIdConnectIssuerUrl')); + this.attrOpenIdConnectIssuer = Token.asString(resource.getAtt('OpenIdConnectIssuer')); } /** diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 296e7beb07fe4..d1fb2bf60352b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -510,8 +510,6 @@ export class Cluster extends Resource implements ICluster { * @param options options for creating a new nodegroup */ public addNodegroup(id: string, options?: NodegroupOptions): Nodegroup { - // initialize the awsAuth for this cluster - this._awsAuth = this._awsAuth ?? this.awsAuth; return new Nodegroup(this, `Nodegroup${id}`, { cluster: this, ...options, @@ -635,6 +633,21 @@ export class Cluster extends Resource implements ICluster { return this._clusterResource.attrOpenIdConnectIssuerUrl; } + /** + * If this cluster is kubectl-enabled, returns the OpenID Connect issuer. + * This is because the values is only be retrieved by the API and not exposed + * by CloudFormation. If this cluster is not kubectl-enabled (i.e. uses the + * stock `CfnCluster`), this is `undefined`. + * @attribute + */ + public get clusterOpenIdConnectIssuer(): string { + if (!this._clusterResource) { + throw new Error('unable to obtain OpenID Connect issuer. Cluster must be kubectl-enabled'); + } + + return this._clusterResource.attrOpenIdConnectIssuer; + } + /** * An `OpenIdConnectProvider` resource associated with this cluster, and which can be used * to link this cluster to AWS IAM. @@ -708,7 +721,7 @@ export class Cluster extends Resource implements ICluster { * @param id the id of this service account * @param options service account options */ - public addServiceAccount(id: string, options: ServiceAccountOptions) { + public addServiceAccount(id: string, options: ServiceAccountOptions = { }) { return new ServiceAccount(this, id, { ...options, cluster: this, diff --git a/packages/@aws-cdk/aws-eks/lib/service-account.ts b/packages/@aws-cdk/aws-eks/lib/service-account.ts index 93d43f700139b..83da66fbfef73 100644 --- a/packages/@aws-cdk/aws-eks/lib/service-account.ts +++ b/packages/@aws-cdk/aws-eks/lib/service-account.ts @@ -1,5 +1,5 @@ import { AddToPrincipalPolicyResult, IPrincipal, IRole, OpenIdConnectPrincipal, PolicyStatement, PrincipalPolicyFragment, Role } from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; +import { CfnJson, Construct } from '@aws-cdk/core'; import { Cluster } from './cluster'; /** @@ -34,7 +34,6 @@ export interface ServiceAccountProps extends ServiceAccountOptions { * Service Account */ export class ServiceAccount extends Construct implements IPrincipal { - /** * The role which is linked to the service account. */ @@ -61,15 +60,25 @@ export class ServiceAccount extends Construct implements IPrincipal { this.serviceAccountName = props.name ?? this.node.uniqueId.toLowerCase(); this.serviceAccountNamespace = props.namespace ?? 'default'; - this.role = new Role(this, 'Role', { - assumedBy: new OpenIdConnectPrincipal(cluster.openIdConnectProvider), + /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. + * See documentation: https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html + */ + const conditions = new CfnJson(this, 'ConditionJson', { + value: { + [`${cluster.clusterOpenIdConnectIssuer}:aud`]: 'sts.amazonaws.com', + [`${cluster.clusterOpenIdConnectIssuer}:sub`]: `system:serviceaccount:${this.serviceAccountNamespace}:${this.serviceAccountName}`, + }, + }); + const principal = new OpenIdConnectPrincipal(cluster.openIdConnectProvider).withConditions({ + StringEquals: conditions, }); + this.role = new Role(this, 'Role', { assumedBy: principal }); this.assumeRoleAction = this.role.assumeRoleAction; this.grantPrincipal = this.role.grantPrincipal; this.policyFragment = this.role.policyFragment; - cluster.addResource('ServiceAccount', { + cluster.addResource(`${id}ServiceAccountResource`, { apiVersion: 'v1', kind: 'ServiceAccount', metadata: { diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json index 982a27933f700..164377d944797 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json @@ -2211,6 +2211,130 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "ClusterMyServiceAccountConditionJson671C0633": { + "Type": "Custom::AWSCDKCfnJson", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AWSCDKCfnUtilsProviderCustomResourceProviderHandlerCF82AA57", + "Arn" + ] + }, + "Value": { + "Fn::Join": [ + "", + [ + "{\"", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "OpenIdConnectIssuer" + ] + }, + ":aud\":\"sts.amazonaws.com\",\"", + { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "OpenIdConnectIssuer" + ] + }, + ":sub\":\"system:serviceaccount:default:awscdkeksclustertestclustermyserviceaccount4080bcdd\"}" + ] + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClusterMyServiceAccountRole85337B29": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "Fn::GetAtt": [ + "ClusterMyServiceAccountConditionJson671C0633", + "Value" + ] + } + }, + "Effect": "Allow", + "Principal": { + "Federated": { + "Ref": "ClusterOpenIdConnectProviderE7EB0530" + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ClusterOpenIdConnectProviderE7EB0530": { + "Type": "Custom::AWSCDKOpenIdConnectProvider", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderHandlerF2C543E0", + "Arn" + ] + }, + "ClientIDList": [ + "sts.amazonaws.com" + ], + "ThumbprintList": [ + "9e99a48a9960b14926bb7f3b02e22da2b0ab7280" + ], + "Url": { + "Fn::GetAtt": [ + "Cluster9EE0221C", + "OpenIdConnectIssuerUrl" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ClustermanifestMyServiceAccountServiceAccountResource0EC03615": { + "Type": "Custom::AWSCDK-EKS-KubernetesResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B", + "Outputs.awscdkeksclustertestawscdkawseksKubectlProviderframeworkonEventC681B49AArn" + ] + }, + "Manifest": { + "Fn::Join": [ + "", + [ + "[{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"awscdkeksclustertestclustermyserviceaccount4080bcdd\",\"namespace\":\"default\",\"labels\":{\"app.kubernetes.io/name\":\"awscdkeksclustertestclustermyserviceaccount4080bcdd\"},\"annotations\":{\"eks.amazonaws.com/role-arn\":\"", + { + "Fn::GetAtt": [ + "ClusterMyServiceAccountRole85337B29", + "Arn" + ] + }, + "\"}}}]" + ] + ] + }, + "ClusterName": { + "Ref": "Cluster9EE0221C" + }, + "RoleArn": { + "Fn::GetAtt": [ + "ClusterCreationRole360249B6", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, "awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454": { "Type": "AWS::CloudFormation::Stack", "Properties": { @@ -2224,7 +2348,7 @@ }, "/", { - "Ref": "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9S3BucketDF419A16" + "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3BucketB18DC500" }, "/", { @@ -2234,7 +2358,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9S3VersionKeyAA30989B" + "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A" } ] } @@ -2247,7 +2371,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9S3VersionKeyAA30989B" + "Ref": "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A" } ] } @@ -2257,11 +2381,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3Bucket21CE03E4Ref": { - "Ref": "AssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3BucketE9BEFBC2" + "referencetoawscdkeksclustertestAssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket35BE45A3Ref": { + "Ref": "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket221B7FEE" }, - "referencetoawscdkeksclustertestAssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3VersionKey7161DBC6Ref": { - "Ref": "AssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3VersionKeyC7391006" + "referencetoawscdkeksclustertestAssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKey60905A80Ref": { + "Ref": "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKeyA8C9A018" }, "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" @@ -2332,6 +2456,183 @@ } } } + }, + "AWSCDKCfnUtilsProviderCustomResourceProviderRoleFE0EE867": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "AWSCDKCfnUtilsProviderCustomResourceProviderHandlerCF82AA57": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E" + } + ] + } + ] + } + ] + ] + } + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "AWSCDKCfnUtilsProviderCustomResourceProviderRoleFE0EE867", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "AWSCDKCfnUtilsProviderCustomResourceProviderRoleFE0EE867" + ] + }, + "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderRole517FED65": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": "*", + "Action": [ + "iam:CreateOpenIDConnectProvider", + "iam:DeleteOpenIDConnectProvider", + "iam:UpdateOpenIDConnectProviderThumbprint", + "iam:AddClientIDToOpenIDConnectProvider", + "iam:RemoveClientIDFromOpenIDConnectProvider" + ] + } + ] + } + } + ] + } + }, + "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderHandlerF2C543E0": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3" + } + ] + } + ] + } + ] + ] + } + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderRole517FED65", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + }, + "DependsOn": [ + "CustomAWSCDKOpenIdConnectProviderCustomResourceProviderRole517FED65" + ] } }, "Outputs": { @@ -2406,17 +2707,17 @@ } }, "Parameters": { - "AssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3BucketE9BEFBC2": { + "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3Bucket221B7FEE": { "Type": "String", - "Description": "S3 bucket for asset \"35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebe\"" + "Description": "S3 bucket for asset \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" }, - "AssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeS3VersionKeyC7391006": { + "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896S3VersionKeyA8C9A018": { "Type": "String", - "Description": "S3 key for asset version \"35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebe\"" + "Description": "S3 key for asset version \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" }, - "AssetParameters35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebeArtifactHash058BD37E": { + "AssetParameters01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896ArtifactHashED8C0EF9": { "Type": "String", - "Description": "Artifact hash for asset \"35ffa1014d8c5abddd237ff0b1d26a4d2972126d08cb88e44350b31fcc1a3ebe\"" + "Description": "Artifact hash for asset \"01ec3fa8451b6541733a25ec9c0c13a2b7dcee848ddad2edf6cb9c1f40cbc896\"" }, "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { "Type": "String", @@ -2442,17 +2743,41 @@ "Type": "String", "Description": "Artifact hash for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" }, - "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9S3BucketDF419A16": { + "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3BucketEF5DD638": { + "Type": "String", + "Description": "S3 bucket for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + }, + "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57S3VersionKey2EA0DB2E": { + "Type": "String", + "Description": "S3 key for asset version \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + }, + "AssetParameterse02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57ArtifactHash95B71D2D": { + "Type": "String", + "Description": "Artifact hash for asset \"e02a38b06730095e29b3afe60b65afcdc3a4ad4716c2f21de5fd5dc58e194f57\"" + }, + "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3Bucket718B603F": { + "Type": "String", + "Description": "S3 bucket for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + }, + "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319S3VersionKey6B97A1A3": { + "Type": "String", + "Description": "S3 key for asset version \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + }, + "AssetParameters4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319ArtifactHash96BDDF33": { + "Type": "String", + "Description": "Artifact hash for asset \"4c04b604b3ea48cf40394c3b4b898525a99ce5f981bc13ad94bf126997416319\"" + }, + "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3BucketB18DC500": { "Type": "String", - "Description": "S3 bucket for asset \"01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9\"" + "Description": "S3 bucket for asset \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" }, - "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9S3VersionKeyAA30989B": { + "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6S3VersionKeyBE7DFF7A": { "Type": "String", - "Description": "S3 key for asset version \"01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9\"" + "Description": "S3 key for asset version \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" }, - "AssetParameters01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9ArtifactHash93C28EE4": { + "AssetParameters7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6ArtifactHash5F906FBC": { "Type": "String", - "Description": "Artifact hash for asset \"01e05ecb4864cd6d6d0dd901e087371b7ba00c9b173029d9a51e5a0b6d450dd9\"" + "Description": "Artifact hash for asset \"7c148fb102ee8790aaf67d5e2a2dce8f5d9b87285c8b7e91f984216ee66f1be6\"" }, "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF": { "Type": "String", diff --git a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts index a6eda3ab3eeee..f6e883f773140 100644 --- a/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts @@ -66,6 +66,9 @@ class EksClusterStack extends TestStack { cluster.addChart('dashboard', { chart: 'kubernetes-dashboard', repository: 'https://kubernetes-charts.storage.googleapis.com' }); cluster.addChart('nginx-ingress', { chart: 'nginx-ingress', repository: 'https://helm.nginx.com/stable', namespace: 'kube-system' }); + // add a service account connected to a IAM role + cluster.addServiceAccount('MyServiceAccount'); + new CfnOutput(this, 'ClusterEndpoint', { value: cluster.clusterEndpoint }); new CfnOutput(this, 'ClusterArn', { value: cluster.clusterArn }); new CfnOutput(this, 'ClusterCertificateAuthorityData', { value: cluster.clusterCertificateAuthorityData }); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts index 311116611a58f..29dcfac4e89b6 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster-resource-provider.ts @@ -100,6 +100,7 @@ export = { Arn: 'arn:cluster-arn', CertificateAuthorityData: 'certificateAuthority-data', OpenIdConnectIssuerUrl: undefined, + OpenIdConnectIssuer: undefined, }, }); test.done(); @@ -422,6 +423,7 @@ export = { Arn: 'arn:cluster-arn', CertificateAuthorityData: 'certificateAuthority-data', OpenIdConnectIssuerUrl: undefined, + OpenIdConnectIssuer: undefined, }, }); test.done(); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index c99324b0a8ea1..daeded7e80743 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -856,18 +856,6 @@ export = { ], }, }, - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - AWS: { - 'Fn::GetAtt': [ - 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', - 'Outputs.StackawscdkawseksKubectlProviderHandlerServiceRole2C52B3ECArn', - ], - }, - }, - }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts index b22918dd1b306..acd5871c6cb6b 100644 --- a/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts +++ b/packages/@aws-cdk/aws-eks/test/test.nodegroup.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { countResources, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -90,6 +90,18 @@ export = { )); test.done(); }, + 'create nodegroups with kubectlEnabled is false'(test: Test) { + // GIVEN + const { stack, vpc } = testFixture(); + + // WHEN + const cluster = new eks.Cluster(stack, 'Cluster', { vpc, kubectlEnabled: false, defaultCapacity: 2 }); + // add a extra nodegroup + cluster.addNodegroup('extra-ng'); + // THEN + expect(stack).to(countResources('AWS::EKS::Nodegroup', 2)); + test.done(); + }, 'create nodegroup with instanceType provided'(test: Test) { // GIVEN const { stack, vpc } = testFixture(); diff --git a/packages/@aws-cdk/aws-eks/test/test.service-account.ts b/packages/@aws-cdk/aws-eks/test/test.service-account.ts index 9a3467023b47c..8c83c62da2810 100644 --- a/packages/@aws-cdk/aws-eks/test/test.service-account.ts +++ b/packages/@aws-cdk/aws-eks/test/test.service-account.ts @@ -50,6 +50,14 @@ export = { Ref: 'ClusterOpenIdConnectProviderE7EB0530', }, }, + Condition: { + StringEquals: { + 'Fn::GetAtt': [ + 'MyServiceAccountConditionJson1ED3BC54', + 'Value', + ], + }, + }, }, ], Version: '2012-10-17', @@ -57,5 +65,50 @@ export = { })); test.done(); }, + 'should have allow multiple services accounts'(test: Test) { + // GIVEN + const { stack, cluster } = testFixtureCluster(); + + // WHEN + cluster.addServiceAccount('MyServiceAccount'); + cluster.addServiceAccount('MyOtherServiceAccount'); + + // THEN + expect(stack).to(haveResource(eks.KubernetesResource.RESOURCE_TYPE, { + ServiceToken: { + 'Fn::GetAtt': [ + 'awscdkawseksKubectlProviderNestedStackawscdkawseksKubectlProviderNestedStackResourceA7AEBA6B', + 'Outputs.StackawscdkawseksKubectlProviderframeworkonEvent8897FD9BArn', + ], + }, + Manifest: { + 'Fn::Join': [ + '', + [ + '[{\"apiVersion\":\"v1\",\"kind\":\"ServiceAccount\",\"metadata\":{\"name\":\"stackclustermyotherserviceaccounta472761a\",\"namespace\":\"default\",\"labels\":{\"app.kubernetes.io/name\":\"stackclustermyotherserviceaccounta472761a\"},\"annotations\":{\"eks.amazonaws.com/role-arn\":\"', + { + 'Fn::GetAtt': [ + 'ClusterMyOtherServiceAccountRole764583C5', + 'Arn', + ], + }, + '\"}}}]', + ], + ], + }, + })); + test.done(); + }, + 'should have unique resource name'(test: Test) { + // GIVEN + const { cluster } = testFixtureCluster(); + + // WHEN + cluster.addServiceAccount('MyServiceAccount'); + + // THEN + test.throws(() => cluster.addServiceAccount('MyServiceAccount')); + test.done(); + }, }, }; diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md index 22764ebe22de1..4ef361dd5512c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/README.md @@ -64,16 +64,17 @@ updated to allow the network traffic. #### Conditions It's possible to route traffic to targets based on conditions in the incoming -HTTP request. Path- and host-based conditions are supported. For example, the -following will route requests to the indicated AutoScalingGroup only if the -requested host in the request is either for `example.com/ok` or -`example.com/path`: +HTTP request. For example, the following will route requests to the indicated +AutoScalingGroup only if the requested host in the request is either for +`example.com/ok` or `example.com/path`: ```ts listener.addTargets('Example.Com Fleet', { priority: 10, - pathPatterns: ['/ok', '/path'], - hostHeader: 'example.com', + conditions: [ + ListenerCondition.hostHeaders(['example.com']), + ListenerCondition.pathPatterns(['/ok', '/path']), + ], port: 8080, targets: [asg] }); @@ -126,8 +127,10 @@ Here's an example of serving a fixed response at the `/ok` URL: ```ts listener.addAction('Fixed', { - pathPatterns: ['/ok'], priority: 10, + conditions: [ + ListenerCondition.pathPatterns(['/ok']), + ], action: ListenerAction.fixedResponse(200, { contentType: elbv2.ContentType.TEXT_PLAIN, messageBody: 'OK', 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 c2eab3160fd6a..a8de884e9f2c0 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 @@ -4,6 +4,7 @@ import { IListenerAction } from '../shared/listener-action'; import { IApplicationListener } from './application-listener'; import { ListenerAction } from './application-listener-action'; import { IApplicationTargetGroup } from './application-target-group'; +import { ListenerCondition } from './conditions'; /** * Basic properties for defining a rule on a listener @@ -58,6 +59,15 @@ export interface BaseApplicationListenerRuleProps { */ readonly redirectResponse?: RedirectResponse; + /** + * Rule applies if matches the conditions. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html + * + * @default - No conditions. + */ + readonly conditions?: ListenerCondition[]; + /** * Rule applies if the requested host matches the indicated host * @@ -66,6 +76,7 @@ export interface BaseApplicationListenerRuleProps { * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#host-conditions * * @default - No host condition. + * @deprecated Use `conditions` instead. */ readonly hostHeader?: string; @@ -74,7 +85,7 @@ export interface BaseApplicationListenerRuleProps { * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions * @default - No path condition. - * @deprecated Use `pathPatterns` instead. + * @deprecated Use `conditions` instead. */ readonly pathPattern?: string; @@ -85,6 +96,7 @@ export interface BaseApplicationListenerRuleProps { * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions * @default - No path conditions. + * @deprecated Use `conditions` instead. */ readonly pathPatterns?: string[]; } @@ -187,7 +199,8 @@ export class ApplicationListenerRule extends cdk.Construct { */ public readonly listenerRuleArn: string; - private readonly conditions: {[key: string]: string[] | undefined} = {}; + private readonly conditions: ListenerCondition[]; + private readonly legacyConditions: {[key: string]: string[]} = {}; private readonly listener: IApplicationListener; private action?: IListenerAction; @@ -195,9 +208,11 @@ export class ApplicationListenerRule extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: ApplicationListenerRuleProps) { super(scope, id); + this.conditions = props.conditions || []; + const hasPathPatterns = props.pathPatterns || props.pathPattern; - if (!props.hostHeader && !hasPathPatterns) { - throw new Error('At least one of \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.'); + if (this.conditions.length === 0 && !props.hostHeader && !hasPathPatterns) { + throw new Error('At least one of \'conditions\', \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.'); } const possibleActions: Array = ['action', 'targetGroups', 'fixedResponse', 'redirectResponse']; @@ -248,9 +263,25 @@ export class ApplicationListenerRule extends cdk.Construct { /** * Add a non-standard condition to this rule + * + * If the condition conflicts with an already set condition, it will be overwritten by the one you specified. + * + * @deprecated use `addCondition` instead. */ public setCondition(field: string, values: string[] | undefined) { - this.conditions[field] = values; + if (values === undefined) { + delete this.legacyConditions[field]; + return; + } + + this.legacyConditions[field] = values; + } + + /** + * Add a non-standard condition to this rule + */ + public addCondition(condition: ListenerCondition) { + this.conditions.push(condition); } /** @@ -322,20 +353,28 @@ export class ApplicationListenerRule extends cdk.Construct { if (this.action === undefined) { return ['Listener rule needs at least one action']; } + + const legacyConditionFields = Object.keys(this.legacyConditions); + if (legacyConditionFields.length === 0 && this.conditions.length === 0) { + return ['Listener rule needs at least one condition']; + } + return []; } /** * Render the conditions for this rule */ - private renderConditions() { - const ret = new Array<{ field: string, values: string[] }>(); - for (const [field, values] of Object.entries(this.conditions)) { - if (values !== undefined) { - ret.push({ field, values }); - } - } - return ret; + private renderConditions(): any { + const legacyConditions = Object.entries(this.legacyConditions).map(([field, values]) => { + return { field, values }; + }); + const conditions = this.conditions.map(condition => condition.renderRawCondition()); + + return [ + ...legacyConditions, + ...conditions, + ]; } } 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 c3560eb519993..c087f690f70e5 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -10,6 +10,7 @@ import { ApplicationListenerCertificate } from './application-listener-certifica import { ApplicationListenerRule, FixedResponse, RedirectResponse, validateFixedResponse, validateRedirectResponse } from './application-listener-rule'; import { IApplicationLoadBalancer } from './application-load-balancer'; import { ApplicationTargetGroup, IApplicationLoadBalancerTarget, IApplicationTargetGroup } from './application-target-group'; +import { ListenerCondition } from './conditions'; /** * Basic properties for an ApplicationListener @@ -274,6 +275,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis // TargetGroup.registerListener is called inside ApplicationListenerRule. new ApplicationListenerRule(this, id + 'Rule', { listener: this, + conditions: props.conditions, hostHeader: props.hostHeader, pathPattern: props.pathPattern, pathPatterns: props.pathPatterns, @@ -319,6 +321,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis }); this.addTargetGroups(id, { + conditions: props.conditions, hostHeader: props.hostHeader, pathPattern: props.pathPattern, pathPatterns: props.pathPatterns, @@ -618,6 +621,15 @@ export interface AddRuleProps { */ readonly priority?: number; + /** + * Rule applies if matches the conditions. + * + * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html + * + * @default - No conditions. + */ + readonly conditions?: ListenerCondition[]; + /** * Rule applies if the requested host matches the indicated host * @@ -628,6 +640,7 @@ export interface AddRuleProps { * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#host-conditions * * @default No host condition + * @deprecated Use `conditions` instead. */ readonly hostHeader?: string; @@ -640,7 +653,7 @@ export interface AddRuleProps { * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions * @default No path condition - * @deprecated Use `pathPatterns` instead. + * @deprecated Use `conditions` instead. */ readonly pathPattern?: string; @@ -653,6 +666,7 @@ export interface AddRuleProps { * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#path-conditions * @default - No path condition. + * @deprecated Use `conditions` instead. */ readonly pathPatterns?: string[]; } @@ -770,7 +784,11 @@ export interface AddRedirectResponseProps extends AddRuleProps, RedirectResponse } function checkAddRuleProps(props: AddRuleProps) { - if ((props.hostHeader !== undefined || props.pathPattern !== undefined || props.pathPatterns !== undefined) !== (props.priority !== undefined)) { - throw new Error('Setting \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa'); + const conditionsCount = props.conditions?.length || 0; + const hasAnyConditions = conditionsCount !== 0 || + props.hostHeader !== undefined || props.pathPattern !== undefined || props.pathPatterns !== undefined; + const hasPriority = props.priority !== undefined; + if (hasAnyConditions !== hasPriority) { + throw new Error('Setting \'conditions\', \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa'); } } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/conditions.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/conditions.ts new file mode 100644 index 0000000000000..ba5ebce6f70e4 --- /dev/null +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/conditions.ts @@ -0,0 +1,190 @@ +/** + * ListenerCondition providers definition. + */ +export abstract class ListenerCondition { + /** + * Create a host-header listener rule condition + * + * @param values Hosts for host headers + */ + public static hostHeaders(values: string[]): ListenerCondition { + return new HostHeaderListenerCondition(values); + } + + /** + * Create a http-header listener rule condition + * + * @param name HTTP header name + * @param values HTTP header values + */ + public static httpHeader(name: string, values: string[]): ListenerCondition { + return new HttpHeaderListenerCondition(name, values); + } + + /** + * Create a http-request-method listener rule condition + * + * @param values HTTP request methods + */ + public static httpRequestMethods(values: string[]): ListenerCondition { + return new HttpRequestMethodListenerCondition(values); + } + + /** + * Create a path-pattern listener rule condition + * + * @param values Path patterns + */ + public static pathPatterns(values: string[]): ListenerCondition { + return new PathPatternListenerCondition(values); + } + + /** + * Create a query-string listener rule condition + * + * @param values Query string key/value pairs + */ + public static queryStrings(values: QueryStringCondition[]): ListenerCondition { + return new QueryStringListenerCondition(values); + } + + /** + * Create a source-ip listener rule condition + * + * @param values Source ips + */ + public static sourceIps(values: string[]): ListenerCondition { + return new SourceIpListenerCondition(values); + } + + /** + * Render the raw Cfn listener rule condition object. + */ + public abstract renderRawCondition(): any; +} + +/** + * Properties for the key/value pair of the query string + */ +export interface QueryStringCondition { + /** + * The query string key for the condition + * + * @default - Any key can be matched. + */ + readonly key?: string; + + /** + * The query string value for the condition + */ + readonly value: string; +} + +/** + * Host header config of the listener rule condition + */ +class HostHeaderListenerCondition extends ListenerCondition { + constructor(public readonly values: string[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'host-header', + hostHeaderConfig: { + values: this.values, + }, + }; + } +} + +/** + * HTTP header config of the listener rule condition + */ +class HttpHeaderListenerCondition extends ListenerCondition { + constructor(public readonly name: string, public readonly values: string[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'http-header', + httpHeaderConfig: { + httpHeaderName: this.name, + values: this.values, + }, + }; + } +} + +/** + * HTTP reqeust method config of the listener rule condition + */ +class HttpRequestMethodListenerCondition extends ListenerCondition { + constructor(public readonly values: string[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'http-request-method', + httpRequestMethodConfig: { + values: this.values, + }, + }; + } +} + +/** + * Path pattern config of the listener rule condition + */ +class PathPatternListenerCondition extends ListenerCondition { + constructor(public readonly values: string[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'path-pattern', + pathPatternConfig: { + values: this.values, + }, + }; + } +} + +/** + * Query string config of the listener rule condition + */ +class QueryStringListenerCondition extends ListenerCondition { + constructor(public readonly values: QueryStringCondition[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'query-string', + queryStringConfig: { + values: this.values, + }, + }; + } +} + +/** + * Source ip config of the listener rule condition + */ +class SourceIpListenerCondition extends ListenerCondition { + constructor(public readonly values: string[]) { + super(); + } + + public renderRawCondition(): any { + return { + field: 'source-ip', + sourceIpConfig: { + values: this.values, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts index 1442972c82a50..9f8833b15bfda 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/index.ts @@ -7,6 +7,7 @@ export * from './alb/application-listener-rule'; export * from './alb/application-load-balancer'; export * from './alb/application-target-group'; export * from './alb/application-listener-action'; +export * from './alb/conditions'; export * from './nlb/network-listener'; export * from './nlb/network-load-balancer'; 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 7588119a3762f..825e4472db7cb 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -993,7 +993,253 @@ export = { test.done(); }, - 'Add path patterns to imported application listener'(test: Test) { + 'Add additonal condition to listener rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); + const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); + + // WHEN + const listener = lb.addListener('Listener', { + port: 443, + certificateArns: ['cert1'], + defaultTargetGroups: [group2], + }); + listener.addTargetGroups('TargetGroup1', { + priority: 10, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + elbv2.ListenerCondition.httpHeader('Accept', ['application/vnd.myapp.v2+json']), + ], + targetGroups: [group1], + }); + listener.addTargetGroups('TargetGroup2', { + priority: 20, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + ], + targetGroups: [group2], + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 10, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + { + Field: 'http-header', + HttpHeaderConfig: { + HttpHeaderName: 'Accept', + Values: ['application/vnd.myapp.v2+json'], + }, + }, + ], + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 20, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + ], + })); + + test.done(); + }, + + 'Add multiple additonal condition to listener rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); + const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); + const group3 = new elbv2.ApplicationTargetGroup(stack, 'Group3', { vpc, port: 82, protocol: elbv2.ApplicationProtocol.HTTP }); + + // WHEN + const listener = lb.addListener('Listener', { + port: 443, + certificateArns: ['cert1'], + defaultTargetGroups: [group3], + }); + listener.addTargetGroups('TargetGroup1', { + priority: 10, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + elbv2.ListenerCondition.sourceIps(['192.0.2.0/24']), + elbv2.ListenerCondition.queryStrings([{ key: 'version', value: '2' }, { value: 'foo*' }]), + ], + targetGroups: [group1], + }); + listener.addTargetGroups('TargetGroup2', { + priority: 20, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + elbv2.ListenerCondition.httpHeader('Accept', ['application/vnd.myapp.v2+json']), + ], + targetGroups: [group1], + }); + listener.addTargetGroups('TargetGroup3', { + priority: 30, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + elbv2.ListenerCondition.httpRequestMethods(['PUT', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK']), + ], + targetGroups: [group2], + }); + listener.addTargetGroups('TargetGroup4', { + priority: 40, + conditions: [ + elbv2.ListenerCondition.hostHeaders(['app.test']), + ], + targetGroups: [group3], + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 10, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + { + Field: 'source-ip', + SourceIpConfig: { + Values: ['192.0.2.0/24'], + }, + }, + { + Field: 'query-string', + QueryStringConfig: { + Values: [ + { + Key: 'version', + Value: '2', + }, + { + Value: 'foo*', + }, + ], + }, + }, + ], + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 20, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + { + Field: 'http-header', + HttpHeaderConfig: { + HttpHeaderName: 'Accept', + Values: ['application/vnd.myapp.v2+json'], + }, + }, + ], + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 30, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + { + Field: 'http-request-method', + HttpRequestMethodConfig: { + Values: ['PUT', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'], + }, + }, + ], + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 40, + Conditions: [ + { + Field: 'host-header', + HostHeaderConfig: { + Values: ['app.test'], + }, + }, + ], + })); + + test.done(); + }, + + 'Can exist together legacy style conditions and modan style conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + const group1 = new elbv2.ApplicationTargetGroup(stack, 'Group1', { vpc, port: 80 }); + const group2 = new elbv2.ApplicationTargetGroup(stack, 'Group2', { vpc, port: 81, protocol: elbv2.ApplicationProtocol.HTTP }); + + // WHEN + const listener = lb.addListener('Listener', { + port: 443, + certificateArns: ['cert1'], + defaultTargetGroups: [group2], + }); + listener.addTargetGroups('TargetGroup1', { + hostHeader: 'app.test', + pathPattern: '/test', + conditions: [ + elbv2.ListenerCondition.sourceIps(['192.0.2.0/24']), + ], + priority: 10, + targetGroups: [group1], + }); + + // THEN + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::ListenerRule', { + Priority: 10, + Conditions: [ + { + Field: 'host-header', + Values: ['app.test'], + }, + { + Field: 'path-pattern', + Values: ['/test'], + }, + { + Field: 'source-ip', + SourceIpConfig: { + Values: ['192.0.2.0/24'], + }, + }, + ], + })); + + test.done(); + }, + + 'Add condition to imported application listener'(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'Stack'); diff --git a/packages/@aws-cdk/aws-events-targets/build-tools/gen.js b/packages/@aws-cdk/aws-events-targets/build-tools/gen.js new file mode 100644 index 0000000000000..406c72cd9b11e --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/build-tools/gen.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +/** + * Writes lib/sdk-api-metadata.generated.ts from the metadata gathered from the + * aws-sdk package. + */ + +const fs = require('fs'); +const path = require('path'); + +const packageInfo = require('aws-sdk/package.json'); +const sdkMetadata = require('aws-sdk/apis/metadata.json'); + +fs.writeFileSync( + path.resolve(__dirname, '..', 'lib', 'sdk-api-metadata.generated.ts'), + [ + 'export interface AwsSdkMetadata {', + ' readonly [service: string]: {', + ' readonly name: string;', + ' readonly cors?: boolean;', + ' readonly dualstackAvailable?: boolean;', + ' readonly prefix?: string;', + ' readonly versions?: readonly string[];', + ' readonly xmlNoDefaultLists?: boolean;', + ' readonly [key: string]: unknown;', + ' };', + '}', + '', + // The generated code is probably not going to be super clean as far as linters are concerned... + '/* eslint-disable */', + '/* tslint:disable */', + '', + // Just mention where the data comes from, as a basic courtesy... + '/**', + ` * Extracted from ${packageInfo.name} version ${packageInfo.version} (${packageInfo.license}).`, + ' */', + // And finally, we export the data: + `export const metadata: AwsSdkMetadata = ${JSON.stringify(sdkMetadata, null, 2)};`, + ].join('\n'), +); diff --git a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts index fdf0ac50eafa0..b47f23e6e9a2b 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/aws-api.ts @@ -2,7 +2,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as path from 'path'; -import * as metadata from './sdk-api-metadata.json'; +import { metadata } from './sdk-api-metadata.generated'; import { addLambdaPermission } from './util'; /** diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 5142667aed462..0216eabf50638 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -48,7 +48,7 @@ }, "cdk-build": { "pre": [ - "cp -f $(node -p 'require.resolve(\"aws-sdk/apis/metadata.json\")') lib/sdk-api-metadata.json && rm -f lib/sdk-api-metadata.d.ts" + "node ./build-tools/gen.js" ], "jest": true }, diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index e8e7cfcfa3431..582cd570e8bc8 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -244,7 +244,7 @@ export class Rule extends Resource implements IRule { }); new CfnEventBusPolicy(eventBusPolicyStack, 'GivePermToOtherAccount', { action: 'events:PutEvents', - statementId: 'MySid', + statementId: `Allow-account-${sourceAccount}`, principal: sourceAccount, }); } diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index b478fb10ec0fa..304bf91ed4dcb 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -717,7 +717,7 @@ export = { const eventBusPolicyStack = app.node.findChild(`EventBusPolicy-${sourceAccount}-us-west-2-${targetAccount}`) as cdk.Stack; expect(eventBusPolicyStack).to(haveResourceLike('AWS::Events::EventBusPolicy', { 'Action': 'events:PutEvents', - 'StatementId': 'MySid', + 'StatementId': `Allow-account-${sourceAccount}`, 'Principal': sourceAccount, })); diff --git a/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js b/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js new file mode 100644 index 0000000000000..a9d39af55b7e5 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/.gitignore b/packages/@aws-cdk/aws-globalaccelerator/.gitignore new file mode 100644 index 0000000000000..e9fee23607e76 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +tslint.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js diff --git a/packages/@aws-cdk/aws-globalaccelerator/.npmignore b/packages/@aws-cdk/aws-globalaccelerator/.npmignore new file mode 100644 index 0000000000000..fb37683c5a457 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/.npmignore @@ -0,0 +1,23 @@ +# 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 + +tsconfig.json + +.eslintrc.js +jest.config.js diff --git a/packages/@aws-cdk/aws-globalaccelerator/LICENSE b/packages/@aws-cdk/aws-globalaccelerator/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/LICENSE @@ -0,0 +1,201 @@ + 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-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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-globalaccelerator/NOTICE b/packages/@aws-cdk/aws-globalaccelerator/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-globalaccelerator/README.md b/packages/@aws-cdk/aws-globalaccelerator/README.md new file mode 100644 index 0000000000000..1765922b5589b --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/README.md @@ -0,0 +1,16 @@ +## AWS::GlobalAccelerator Construct Library + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import globalaccelerator = require('@aws-cdk/aws-globalaccelerator'); +``` diff --git a/packages/@aws-cdk/aws-globalaccelerator/jest.config.js b/packages/@aws-cdk/aws-globalaccelerator/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts new file mode 100644 index 0000000000000..32d3860d45724 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::GlobalAccelerator CloudFormation Resources: +export * from './globalaccelerator.generated'; diff --git a/packages/@aws-cdk/aws-globalaccelerator/package.json b/packages/@aws-cdk/aws-globalaccelerator/package.json new file mode 100644 index 0000000000000..4fab6990574a5 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-cdk/aws-globalaccelerator", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::GlobalAccelerator", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.GlobalAccelerator", + "packageId": "Amazon.CDK.AWS.GlobalAccelerator", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.globalaccelerator", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "globalaccelerator" + } + }, + "python": { + "distName": "aws-cdk.aws-globalaccelerator", + "module": "aws_cdk.aws_globalaccelerator" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-globalaccelerator" + }, + "homepage": "https://github.com/aws/aws-cdk", + "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", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "cdk-build": { + "cloudformation": "AWS::GlobalAccelerator", + "jest": true + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::GlobalAccelerator", + "aws-globalaccelerator" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-globalaccelerator/test/globalaccelerator.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js b/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js new file mode 100644 index 0000000000000..a9d39af55b7e5 --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-imagebuilder/.gitignore b/packages/@aws-cdk/aws-imagebuilder/.gitignore new file mode 100644 index 0000000000000..e9fee23607e76 --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +tslint.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js diff --git a/packages/@aws-cdk/aws-imagebuilder/.npmignore b/packages/@aws-cdk/aws-imagebuilder/.npmignore new file mode 100644 index 0000000000000..fb37683c5a457 --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/.npmignore @@ -0,0 +1,23 @@ +# 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 + +tsconfig.json + +.eslintrc.js +jest.config.js diff --git a/packages/@aws-cdk/aws-imagebuilder/LICENSE b/packages/@aws-cdk/aws-imagebuilder/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/LICENSE @@ -0,0 +1,201 @@ + 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-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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-imagebuilder/NOTICE b/packages/@aws-cdk/aws-imagebuilder/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-imagebuilder/README.md b/packages/@aws-cdk/aws-imagebuilder/README.md new file mode 100644 index 0000000000000..ef5f4bc99f7fa --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/README.md @@ -0,0 +1,16 @@ +## AWS::ImageBuilder Construct Library + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import imagebuilder = require('@aws-cdk/aws-imagebuilder'); +``` diff --git a/packages/@aws-cdk/aws-imagebuilder/jest.config.js b/packages/@aws-cdk/aws-imagebuilder/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-imagebuilder/lib/index.ts b/packages/@aws-cdk/aws-imagebuilder/lib/index.ts new file mode 100644 index 0000000000000..4f8727183ba0d --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::ImageBuilder CloudFormation Resources: +export * from './imagebuilder.generated'; diff --git a/packages/@aws-cdk/aws-imagebuilder/package.json b/packages/@aws-cdk/aws-imagebuilder/package.json new file mode 100644 index 0000000000000..4137199aeba1f --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-cdk/aws-imagebuilder", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::ImageBuilder", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.ImageBuilder", + "packageId": "Amazon.CDK.AWS.ImageBuilder", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.imagebuilder", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "imagebuilder" + } + }, + "python": { + "distName": "aws-cdk.aws-imagebuilder", + "module": "aws_cdk.aws_imagebuilder" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-imagebuilder" + }, + "homepage": "https://github.com/aws/aws-cdk", + "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", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "cdk-build": { + "cloudformation": "AWS::ImageBuilder", + "jest": true + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::ImageBuilder", + "aws-imagebuilder" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-imagebuilder/test/imagebuilder.test.ts b/packages/@aws-cdk/aws-imagebuilder/test/imagebuilder.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-imagebuilder/test/imagebuilder.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts index 41ad7aa0df53a..dd8e3ba2f8565 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/builder.ts @@ -111,11 +111,11 @@ export class Builder { '-v', `${this.options.projectRoot}:${containerProjectRoot}`, '-v', `${path.resolve(this.options.outDir)}:${containerOutDir}`, ...(this.options.cacheDir ? ['-v', `${path.resolve(this.options.cacheDir)}:${containerCacheDir}`] : []), - '-w', path.dirname(containerEntryPath), + '-w', path.dirname(containerEntryPath).replace(/\\/g, '/'), // Always use POSIX paths in the container 'parcel-bundler', ]; const parcelArgs = [ - 'parcel', 'build', containerEntryPath, + 'parcel', 'build', containerEntryPath.replace(/\\/g, '/'), // Always use POSIX paths in the container '--out-dir', containerOutDir, '--out-file', 'index.js', '--global', this.options.global, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts index e6e32655a187e..6c7f5e41ae3e0 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/builder.test.ts @@ -20,6 +20,10 @@ jest.mock('child_process', () => ({ }), })); +beforeEach(() => { + jest.clearAllMocks(); +}); + test('calls docker with the correct args', () => { const builder = new Builder({ entry: '/project/folder/entry.ts', @@ -58,6 +62,24 @@ test('calls docker with the correct args', () => { ]); }); +test('with Windows paths', () => { + const builder = new Builder({ + entry: 'C:\\my-project\\lib\\entry.ts', + global: 'handler', + outDir: '/out-dir', + cacheDir: '/cache-dir', + nodeDockerTag: 'lts-alpine', + nodeVersion: '12', + projectRoot: 'C:\\my-project', + }); + builder.build(); + + // docker run + expect(spawnSync).toHaveBeenCalledWith('docker', expect.arrayContaining([ + 'parcel', 'build', expect.stringContaining('/lib/entry.ts'), + ])); +}); + test('throws in case of error', () => { const builder = new Builder({ entry: '/project/folder/error', diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 536d2a9e71d9d..92ed4dc61392b 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -49,6 +49,39 @@ to our CDK project directory. This is especially important when we want to share this construct through a library. Different programming languages will have different techniques for bundling resources into libraries. +### Execution Role + +Lambda functions assume an IAM role during execution. In CDK by default, Lambda +functions will use an autogenerated Role if one is not provided. + +The autogenerated Role is automatically given permissions to execute the Lambda +function. To reference the autogenerated Role: + +```ts +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + +fn.role // the Role +``` + +You can also provide your own IAM role. Provided IAM roles will not automatically +be given permissions to execute the Lambda function. To provide a role and grant +it appropriate permissions: + +```ts +const fn = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.NODEJS_10_X, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')), + role: myRole // user-provided role +}); + +myRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")); +myRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaVPCAccessExecutionRole")); // only required if your function lives in a VPC +``` + ### Versions and Aliases You can use diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index f66a67f07e336..ea2d2bf1f18ef 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -99,6 +99,12 @@ export interface FunctionOptions extends EventInvokeConfigOptions { * It controls the permissions that the function will have. The Role must * be assumable by the 'lambda.amazonaws.com' service principal. * + * The default Role automatically has permissions granted for Lambda execution. If you + * provide a Role, you must add the relevant AWS managed policies yourself. + * + * The relevant managed policies are "service-role/AWSLambdaBasicExecutionRole" and + * "service-role/AWSLambdaVPCAccessExecutionRole". + * * @default - A unique role will be generated for this lambda function. * Both supplied and generated roles can always be changed by calling `addToRolePolicy`. */ diff --git a/packages/@aws-cdk/aws-macie/.eslintrc.js b/packages/@aws-cdk/aws-macie/.eslintrc.js new file mode 100644 index 0000000000000..a9d39af55b7e5 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-macie/.gitignore b/packages/@aws-cdk/aws-macie/.gitignore new file mode 100644 index 0000000000000..e9fee23607e76 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +tsconfig.json +tslint.json +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk +nyc.config.js +!.eslintrc.js +!jest.config.js diff --git a/packages/@aws-cdk/aws-macie/.npmignore b/packages/@aws-cdk/aws-macie/.npmignore new file mode 100644 index 0000000000000..fb37683c5a457 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/.npmignore @@ -0,0 +1,23 @@ +# 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 + +tsconfig.json + +.eslintrc.js +jest.config.js diff --git a/packages/@aws-cdk/aws-macie/LICENSE b/packages/@aws-cdk/aws-macie/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-macie/LICENSE @@ -0,0 +1,201 @@ + 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-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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-macie/NOTICE b/packages/@aws-cdk/aws-macie/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-macie/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-macie/README.md b/packages/@aws-cdk/aws-macie/README.md new file mode 100644 index 0000000000000..9f4352257b41e --- /dev/null +++ b/packages/@aws-cdk/aws-macie/README.md @@ -0,0 +1,16 @@ +## AWS::Macie Construct Library + +--- + +![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) + +> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +```ts +import macie = require('@aws-cdk/aws-macie'); +``` diff --git a/packages/@aws-cdk/aws-macie/jest.config.js b/packages/@aws-cdk/aws-macie/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-macie/lib/index.ts b/packages/@aws-cdk/aws-macie/lib/index.ts new file mode 100644 index 0000000000000..3e9701436c653 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Macie CloudFormation Resources: +export * from './macie.generated'; diff --git a/packages/@aws-cdk/aws-macie/package.json b/packages/@aws-cdk/aws-macie/package.json new file mode 100644 index 0000000000000..ec9bf60d76782 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-cdk/aws-macie", + "version": "0.0.0", + "description": "The CDK Construct Library for AWS::Macie", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.Macie", + "packageId": "Amazon.CDK.AWS.Macie", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.macie", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "macie" + } + }, + "python": { + "distName": "aws-cdk.aws-macie", + "module": "aws_cdk.aws_macie" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-macie" + }, + "homepage": "https://github.com/aws/aws-cdk", + "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", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat" + }, + "cdk-build": { + "cloudformation": "AWS::Macie", + "jest": true + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::Macie", + "aws-macie" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "cdk-build-tools": "0.0.0", + "cfn2ts": "0.0.0", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/core": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "cfn-only", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-macie/test/macie.test.ts b/packages/@aws-cdk/aws-macie/test/macie.test.ts new file mode 100644 index 0000000000000..e394ef336bfb4 --- /dev/null +++ b/packages/@aws-cdk/aws-macie/test/macie.test.ts @@ -0,0 +1,6 @@ +import '@aws-cdk/assert/jest'; +import {} from '../lib'; + +test('No tests are specified for this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 7a6e8e8ca9597..070ac5ca1698c 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -23,7 +23,7 @@ your instances will be launched privately or publicly: const cluster = new DatabaseCluster(this, 'Database', { engine: DatabaseClusterEngine.AURORA, masterUser: { - username: 'admin' + username: 'clusteradmin' }, instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md index 92a473d886d10..c7cc3fe389099 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -509,14 +509,13 @@ Step Functions supports [AWS Glue](https://docs.aws.amazon.com/step-functions/la You can call the [`StartJobRun`](https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-jobs-runs.html#aws-glue-api-jobs-runs-StartJobRun) API from a `Task` state. ```ts -new sfn.Task(stack, 'Task', { - task: new tasks.RunGlueJobTask(jobName, { - arguments: { - key: 'value', - }, - timeout: cdk.Duration.minutes(30), - notifyDelayAfter: cdk.Duration.minutes(5), - }), +new GlueStartJobRun(stack, 'Task', { + jobName: 'my-glue-job', + arguments: { + key: 'value', + }, + timeout: cdk.Duration.minutes(30), + notifyDelayAfter: cdk.Duration.minutes(5), }); ``` diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/run-glue-job-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/run-glue-job-task.ts index 854df949c4dc9..fd4722835a52e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/run-glue-job-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/run-glue-job-task.ts @@ -5,6 +5,8 @@ import { getResourceArn } from '../resource-arn-suffix'; /** * Properties for RunGlueJobTask + * + * @deprecated use `GlueStartJobRun` */ export interface RunGlueJobTaskProps { @@ -63,6 +65,8 @@ export interface RunGlueJobTaskProps { * https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-jobs-runs.html#aws-glue-api-jobs-runs-JobRun * * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-glue.html + * + * @deprecated use `GlueStartJobRun` */ export class RunGlueJobTask implements sfn.IStepFunctionsTask { private readonly integrationPattern: sfn.ServiceIntegrationPattern; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts new file mode 100644 index 0000000000000..9df1a6a5ed852 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/glue/start-job-run.ts @@ -0,0 +1,119 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { Construct, Duration, Stack } from '@aws-cdk/core'; +import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; + +/** + * Properties for starting an AWS Glue job as a task + */ +export interface GlueStartJobRunProps extends sfn.TaskStateBaseProps { + + /** + * Glue job name + */ + readonly glueJobName: string; + + /** + * The job arguments specifically for this run. + * + * For this job run, they replace the default arguments set in the job + * definition itself. + * + * @default - Default arguments set in the job definition + */ + readonly arguments?: sfn.TaskInput; + + /** + * The name of the SecurityConfiguration structure to be used with this job run. + * + * This must match the Glue API + * @see https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-common.html#aws-glue-api-regex-oneLine + * + * @default - Default configuration set in the job definition + */ + readonly securityConfiguration?: string; + + /** + * After a job run starts, the number of minutes to wait before sending a job run delay notification. + * + * Must be at least 1 minute. + * + * @default - Default delay set in the job definition + */ + readonly notifyDelayAfter?: Duration; +} + +/** + * Starts an AWS Glue job in a Task state + * + * OUTPUT: the output of this task is a JobRun structure, for details consult + * https://docs.aws.amazon.com/glue/latest/dg/aws-glue-api-jobs-runs.html#aws-glue-api-jobs-runs-JobRun + * + * @see https://docs.aws.amazon.com/step-functions/latest/dg/connect-glue.html + */ +export class GlueStartJobRun extends sfn.TaskStateBase { + private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ + sfn.IntegrationPattern.REQUEST_RESPONSE, + sfn.IntegrationPattern.RUN_JOB, + ]; + + protected readonly taskMetrics?: sfn.TaskMetricsConfig; + protected readonly taskPolicies?: iam.PolicyStatement[]; + + private readonly integrationPattern: sfn.IntegrationPattern; + + constructor(scope: Construct, id: string, private readonly props: GlueStartJobRunProps) { + super(scope, id, props); + this.integrationPattern = props.integrationPattern ?? sfn.IntegrationPattern.REQUEST_RESPONSE; + + validatePatternSupported(this.integrationPattern, GlueStartJobRun.SUPPORTED_INTEGRATION_PATTERNS); + + this.taskPolicies = this.getPolicies(); + + this.taskMetrics = { + metricPrefixSingular: 'GlueJob', + metricPrefixPlural: 'GlueJobs', + metricDimensions: { GlueJobName: this.props.glueJobName }, + }; + } + + protected renderTask(): any { + const notificationProperty = this.props.notifyDelayAfter ? { NotifyDelayAfter: this.props.notifyDelayAfter.toMinutes() } : null; + return { + Resource: integrationResourceArn('glue', 'startJobRun', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + JobName: this.props.glueJobName, + Arguments: this.props.arguments?.value, + Timeout: this.props.timeout?.toMinutes(), + SecurityConfiguration: this.props.securityConfiguration, + NotificationProperty: notificationProperty, + }), + TimeoutSeconds: undefined, + }; + } + + private getPolicies(): iam.PolicyStatement[] { + let iamActions: string[] | undefined; + if (this.integrationPattern === sfn.IntegrationPattern.REQUEST_RESPONSE) { + iamActions = ['glue:StartJobRun']; + } else if (this.integrationPattern === sfn.IntegrationPattern.RUN_JOB) { + iamActions = [ + 'glue:StartJobRun', + 'glue:GetJobRun', + 'glue:GetJobRuns', + 'glue:BatchStopJobRun', + ]; + } + + return [new iam.PolicyStatement({ + resources: [ + Stack.of(this).formatArn({ + service: 'glue', + resource: 'job', + resourceName: this.props.glueJobName, + }), + ], + actions: iamActions, + })]; + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 9759c4a621251..7b45086a4e48e 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -23,5 +23,6 @@ export * from './emr/emr-cancel-step'; export * from './emr/emr-modify-instance-fleet-by-name'; export * from './emr/emr-modify-instance-group-by-name'; export * from './glue/run-glue-job-task'; +export * from './glue/start-job-run'; export * from './batch/run-batch-job'; export * from './dynamodb/call-dynamodb'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json new file mode 100644 index 0000000000000..1f916f6be06f1 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.expected.json @@ -0,0 +1,268 @@ +{ + "Parameters": { + "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3BucketB8F6851B": { + "Type": "String", + "Description": "S3 bucket for asset \"d030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0d\"" + }, + "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3VersionKey7BCC06FC": { + "Type": "String", + "Description": "S3 key for asset version \"d030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0d\"" + }, + "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dArtifactHashEC764944": { + "Type": "String", + "Description": "Artifact hash for asset \"d030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0d\"" + } + }, + "Resources": { + "GlueJobRole1CD031E0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "glue.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSGlueServiceRole" + ] + ] + } + ] + } + }, + "GlueJobRoleDefaultPolicy3D94D6F1": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3BucketB8F6851B" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3BucketB8F6851B" + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "GlueJobRoleDefaultPolicy3D94D6F1", + "Roles": [ + { + "Ref": "GlueJobRole1CD031E0" + } + ] + } + }, + "GlueJob": { + "Type": "AWS::Glue::Job", + "Properties": { + "Command": { + "Name": "glueetl", + "PythonVersion": "3", + "ScriptLocation": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3BucketB8F6851B" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3VersionKey7BCC06FC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersd030bb7913ca422df69f29b2ea678ab4e5085bb3cbb17029e4b101d2dc4e3e0dS3VersionKey7BCC06FC" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "GlueJobRole1CD031E0", + "Arn" + ] + }, + "GlueVersion": "1.0", + "Name": "My Glue Job" + } + }, + "StateMachineRole543B9670": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDA5F7DA8": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "glue:StartJobRun", + "glue:GetJobRun", + "glue:GetJobRuns", + "glue:BatchStopJobRun" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":glue:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":job/My Glue Job" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDA5F7DA8", + "Roles": [ + { + "Ref": "StateMachineRole543B9670" + } + ] + } + }, + "StateMachine81935E76": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start Task\",\"States\":{\"Start Task\":{\"Type\":\"Pass\",\"Next\":\"Glue Job Task\"},\"Glue Job Task\":{\"Next\":\"End Task\",\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::glue:startJobRun.sync\",\"Parameters\":{\"JobName\":\"My Glue Job\",\"Arguments\":{\"--enable-metrics\":\"true\"}}},\"End Task\":{\"Type\":\"Pass\",\"End\":true}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRole543B9670", + "Arn" + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDA5F7DA8", + "StateMachineRole543B9670" + ] + } + }, + "Outputs": { + "StateMachineARNOutput": { + "Value": { + "Ref": "StateMachine81935E76" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.ts new file mode 100644 index 0000000000000..d63e2c5f586cc --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/integ.start-job-run.ts @@ -0,0 +1,63 @@ +import * as glue from '@aws-cdk/aws-glue'; +import * as iam from '@aws-cdk/aws-iam'; +import * as assets from '@aws-cdk/aws-s3-assets'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as path from 'path'; +import { GlueStartJobRun } from '../../lib/glue/start-job-run'; + +/* + * Stack verification steps: + * * aws stepfunctions start-execution --state-machine-arn + * * aws stepfunctions describe-execution --execution-arn + * The "describe-execution" call should eventually return status "SUCCEEDED". + * NOTE: It will take up to 15 minutes for the step function to complete due to the cold start time + * for AWS Glue, which as of 02/2020, is around 10-15 minutes. + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-stepfunctions-integ'); + +const codeAsset = new assets.Asset(stack, 'Glue Job Script', { + path: path.join(__dirname, 'my-glue-script/job.py'), +}); + +const jobRole = new iam.Role(stack, 'Glue Job Role', { + assumedBy: new iam.ServicePrincipal('glue'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSGlueServiceRole'), + ], +}); +codeAsset.grantRead(jobRole); + +const job = new glue.CfnJob(stack, 'Glue Job', { + name: 'My Glue Job', + glueVersion: '1.0', + command: { + name: 'glueetl', + pythonVersion: '3', + scriptLocation: `s3://${codeAsset.s3BucketName}/${codeAsset.s3ObjectKey}`, + }, + role: jobRole.roleArn, +}); + +const jobTask = new GlueStartJobRun(stack, 'Glue Job Task', { + glueJobName: job.name!, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + arguments: sfn.TaskInput.fromObject({ + '--enable-metrics': 'true', + }), +}); + +const startTask = new sfn.Pass(stack, 'Start Task'); +const endTask = new sfn.Pass(stack, 'End Task'); + +const stateMachine = new sfn.StateMachine(stack, 'State Machine', { + definition: sfn.Chain.start(startTask).next(jobTask).next(endTask), +}); + +new cdk.CfnOutput(stack, 'State Machine ARN Output', { + value: stateMachine.stateMachineArn, +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts new file mode 100644 index 0000000000000..e3773bd701966 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/glue/start-job-run.test.ts @@ -0,0 +1,173 @@ +import '@aws-cdk/assert/jest'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import { Duration, Stack } from '@aws-cdk/core'; +import * as tasks from '../../lib'; +import { GlueStartJobRun } from '../../lib/glue/start-job-run'; + +const glueJobName = 'GlueJob'; +let stack: Stack; +beforeEach(() => { + stack = new Stack(); +}); + +test('Invoke glue job with just job ARN', () => { + const task = new GlueStartJobRun(stack, 'Task', { + glueJobName, + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun', + ], + ], + }, + End: true, + Parameters: { + JobName: glueJobName, + }, + }); +}); + +test('Invoke glue job with full properties', () => { + const jobArguments = { + key: 'value', + }; + const timeoutMinutes = 1440; + const glueJobTimeout = Duration.minutes(timeoutMinutes); + const securityConfiguration = 'securityConfiguration'; + const notifyDelayAfterMinutes = 10; + const notifyDelayAfter = Duration.minutes(notifyDelayAfterMinutes); + const task = new GlueStartJobRun(stack, 'Task', { + glueJobName, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + arguments: sfn.TaskInput.fromObject(jobArguments), + timeout: glueJobTimeout, + securityConfiguration, + notifyDelayAfter, + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun.sync', + ], + ], + }, + End: true, + Parameters: { + JobName: glueJobName, + Arguments: jobArguments, + Timeout: timeoutMinutes, + SecurityConfiguration: securityConfiguration, + NotificationProperty: { + NotifyDelayAfter: notifyDelayAfterMinutes, + }, + }, + }); +}); + +test('job arguments can reference state input', () => { + const task = new GlueStartJobRun(stack, 'Task', { + glueJobName, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + arguments: sfn.TaskInput.fromDataAt('$.input'), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::glue:startJobRun.sync', + ], + ], + }, + End: true, + Parameters: { + 'JobName': glueJobName, + 'Arguments.$': '$.input', + }, + }); +}); + +test('permitted role actions limited to start job run if service integration pattern is REQUEST_RESPONSE', () => { + const task = new GlueStartJobRun(stack, 'Task', { + glueJobName, + integrationPattern: sfn.IntegrationPattern.REQUEST_RESPONSE, + }); + + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [{ + Action: 'glue:StartJobRun', + }], + }, + }); +}); + +test('permitted role actions include start, get, and stop job run if service integration pattern is RUN_JOB', () => { + const task = new GlueStartJobRun(stack, 'Task', { + glueJobName, + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + }); + + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [{ + Action: [ + 'glue:StartJobRun', + 'glue:GetJobRun', + 'glue:GetJobRuns', + 'glue:BatchStopJobRun', + ], + }], + }, + }); +}); + +test('Task throws if WAIT_FOR_TASK_TOKEN is supplied as service integration pattern', () => { + expect(() => { + new sfn.Task(stack, 'Task', { + task: new tasks.RunGlueJobTask(glueJobName, { + integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, + }), + }); + }).toThrow(/Invalid Service Integration Pattern: WAIT_FOR_TASK_TOKEN is not supported to call Glue./i); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 557527bd02f1c..b7560ad80883e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -131,20 +131,45 @@ directly in the Amazon States language. ### Pass -A `Pass` state does no work, but it can optionally transform the execution's -JSON state. +A `Pass` state passes its input to its output, without performing work. +Pass states are useful when constructing and debugging state machines. + +The following example injects some fixed data into the state machine through +the `result` field. The `result` field will be added to the input and the result +will be passed as the state's output. ```ts // Makes the current JSON state { ..., "subObject": { "hello": "world" } } const pass = new stepfunctions.Pass(this, 'Add Hello World', { - result: { hello: "world" }, - resultPath: '$.subObject', + result: { hello: 'world' }, + resultPath: '$.subObject', }); // Set the next state pass.next(nextState); ``` +The `Pass` state also supports passing key-value pairs as input. Values can +be static, or selected from the input with a path. + +The following example filters the `greeting` field from the state input +and also injects a field called `otherData`. + +```ts +const pass = new stepfunctions.Pass(this, 'Filter input and inject data', { + parameters: { // input to the pass state + input: stepfunctions.DataAt('$.input.greeting') + otherData: 'some-extra-stuff' + }, +}); +``` + +The object specified in `parameters` will be the input of the `Pass` state. +Since neither `Result` nor `ResultPath` are supplied, the `Pass` state copies +its input through to its output. + +Learn more about the [Pass state](https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-pass-state.html) + ### Wait A `Wait` state waits for a given number of seconds, or until the current time diff --git a/packages/@aws-cdk/aws-stepfunctions/jest.config.js b/packages/@aws-cdk/aws-stepfunctions/jest.config.js index d984ff822379b..cd664e1d069e5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/jest.config.js +++ b/packages/@aws-cdk/aws-stepfunctions/jest.config.js @@ -1,10 +1,2 @@ const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); -module.exports = { - ...baseConfig, - coverageThreshold: { - global: { - ...baseConfig.coverageThreshold.global, - branches: 75, - }, - }, -}; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index e081b8fdc734b..982980456caaa 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -1,5 +1,6 @@ import * as cdk from '@aws-cdk/core'; import {Chain} from '../chain'; +import { FieldUtils } from '../fields'; import {IChainable, INextable} from '../types'; import { StateType } from './private/state-type'; import {renderJsonPath, State } from './state'; @@ -147,7 +148,17 @@ export class Pass extends State implements INextable { Result: this.result ? this.result.value : undefined, ResultPath: renderJsonPath(this.resultPath), ...this.renderInputOutput(), + ...this.renderParameters(), ...this.renderNextEnd(), }; } + + /** + * Render Parameters in ASL JSON format + */ + private renderParameters(): any { + return FieldUtils.renderObject({ + Parameters: this.parameters, + }); + } } diff --git a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts index 723d917986899..9e58281059e8b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/custom-state.test.ts @@ -1,12 +1,16 @@ import '@aws-cdk/assert/jest'; import * as cdk from '@aws-cdk/core'; -import * as stepfunctions from '../lib'; +import * as sfn from '../lib'; +import { render } from './private/render-util'; describe('Custom State', () => { - test('maintains the state Json provided during construction', () => { + let stack: cdk.Stack; + let stateJson: any; + + beforeEach(() => { // GIVEN - const stack = new cdk.Stack(); - const stateJson = { + stack = new cdk.Stack(); + stateJson = { Type: 'Task', Resource: 'arn:aws:states:::dynamodb:putItem', Parameters: { @@ -19,9 +23,11 @@ describe('Custom State', () => { }, ResultPath: null, }; + }); + test('maintains the state Json provided during construction', () => { // WHEN - const customState = new stepfunctions.CustomState(stack, 'Custom', { + const customState = new sfn.CustomState(stack, 'Custom', { stateJson, }); @@ -31,4 +37,38 @@ describe('Custom State', () => { End: true, }); }); -}); + + test('can add a next state to the chain', () => { + // WHEN + const definition = new sfn.CustomState(stack, 'Custom', { + stateJson, + }).next(new sfn.Pass(stack, 'MyPass')); + + // THEN + expect(render(stack, definition)).toStrictEqual( + { + StartAt: 'Custom', + States: { + Custom: { + Next: 'MyPass', + Type: 'Task', + Resource: 'arn:aws:states:::dynamodb:putItem', + Parameters: { + TableName: 'MyTable', + Item: { + id: { + S: 'MyEntry', + }, + }, + }, + ResultPath: null, + }, + MyPass: { + Type: 'Pass', + End: true, + }, + }, + }, + ); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/private/render-util.ts b/packages/@aws-cdk/aws-stepfunctions/test/private/render-util.ts new file mode 100644 index 0000000000000..ceb8998c7da92 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/private/render-util.ts @@ -0,0 +1,12 @@ +import * as cdk from '@aws-cdk/core'; +import * as sfn from '../../lib'; + +/** + * Renders a state machine definition + * + * @param stack stack for the state machine + * @param definition state machine definition + */ +export function render(stack: cdk.Stack, definition: sfn.IChainable) { + return stack.resolve(new sfn.StateGraph(definition.startState, 'Test Graph').toGraphJson()); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts index cc6fd2f7ec486..012a193087b9b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-machine-resources.test.ts @@ -274,6 +274,26 @@ describe('State Machine Resources', () => { }); }), + test('parameters can be selected from the input with a path', () => { + // GIVEN + const stack = new cdk.Stack(); + const task = new stepfunctions.Pass(stack, 'Pass', { + parameters: { + input: stepfunctions.Data.stringAt('$.myField'), + }, + }); + + // WHEN + const taskState = task.toStateJson(); + + // THEN + expect(taskState).toEqual({ End: true, + Parameters: + { 'input.$': '$.myField'}, + Type: 'Pass', + }); + }), + test('State machines must depend on their roles', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/state-transition-metrics.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/state-transition-metrics.test.ts new file mode 100644 index 0000000000000..59a66b15a8445 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/state-transition-metrics.test.ts @@ -0,0 +1,54 @@ +import { Metric } from '@aws-cdk/aws-cloudwatch'; +import { StateTransitionMetric } from '../lib'; + +describe('State Transition Metrics', () => { + test('add a named state transition metric', () => { + // WHEN + const metric = StateTransitionMetric.metric('my-metric'); + + // THEN + verifyTransitionMetric(metric, 'my-metric', 'Average'); + }); + + test('metric for available state transitions.', () => { + // WHEN + const metric = StateTransitionMetric.metricProvisionedBucketSize(); + + // THEN + verifyTransitionMetric(metric, 'ProvisionedBucketSize', 'Average'); + }); + + test('metric for provisioned steady-state execution rate', () => { + // WHEN + const metric = StateTransitionMetric.metricProvisionedRefillRate(); + + // THEN + verifyTransitionMetric(metric, 'ProvisionedRefillRate', 'Average'); + }); + + test('metric for state-transitions per second', () => { + // WHEN + const metric = StateTransitionMetric.metricConsumedCapacity(); + + // THEN + verifyTransitionMetric(metric, 'ConsumedCapacity', 'Average'); + }); + + test('metric for the number of throttled state transitions', () => { + // WHEN + const metric = StateTransitionMetric.metricThrottledEvents(); + + // THEN + verifyTransitionMetric(metric, 'ThrottledEvents', 'Sum'); + }); +}); + +function verifyTransitionMetric(metric: Metric, metricName: string, statistic: string) { + expect(metric).toEqual({ + period: { amount: 5, unit: { label: 'minutes', inMillis: 60000 } }, + dimensions: { ServiceMetric: 'StateTransition' }, + namespace: 'AWS/States', + metricName, + statistic, + }); +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts new file mode 100644 index 0000000000000..623ee29689308 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/task.test.ts @@ -0,0 +1,127 @@ +import { Metric } from '@aws-cdk/aws-cloudwatch'; +import * as cdk from '@aws-cdk/core'; +import * as sfn from '../lib'; + +describe('Task state', () => { + + let stack: cdk.Stack; + let task: sfn.Task; + + beforeEach(() => { + // GIVEN + stack = new cdk.Stack(); + task = new sfn.Task(stack, 'my-task', { + task: new FakeTask(), + }); + }); + + test('get named metric for the task', () => { + // WHEN + const metric = task.metric('my-metric'); + + // THEN + verifyMetric(metric, 'my-metric', 'Sum'); + }); + + test('add metric for number of times the task failed', () => { + // WHEN + const metric = task.metricFailed(); + + // THEN + verifyMetric(metric, 'Failed', 'Sum'); + }); + + test('add metric for number of times the metrics heartbeat timed out', () => { + // WHEN + const metric = task.metricHeartbeatTimedOut(); + + // THEN + verifyMetric(metric, 'HeartbeatTimedOut', 'Sum'); + }); + + test('add metric for task state run time', () => { + // WHEN + const metric = task.metricRunTime(); + + // THEN + verifyMetric(metric, 'RunTime', 'Average'); + }); + + test('add metric for task schedule time', () => { + // WHEN + const metric = task.metricScheduleTime(); + + // THEN + verifyMetric(metric, 'ScheduleTime', 'Average'); + }); + + test('add metric for number of times the task is scheduled', () => { + // WHEN + const metric = task.metricScheduled(); + + // THEN + verifyMetric(metric, 'Scheduled', 'Sum'); + }); + + test('add metric for number of times the task was started', () => { + // WHEN + const metric = task.metricStarted(); + + // THEN + verifyMetric(metric, 'Started', 'Sum'); + }); + + test('add metric for number of times the task succeeded', () => { + // WHEN + const metric = task.metricSucceeded(); + + // THEN + verifyMetric(metric, 'Succeeded', 'Sum'); + }); + + test('add metric for time between task being scheduled to closing', () => { + // WHEN + const metric = task.metricTime(); + + // THEN + verifyMetric(metric, 'Time', 'Average'); + }); + + test('add metric for number of times the task times out', () => { + // WHEN + const metric = task.metricTimedOut(); + + // THEN + verifyMetric(metric, 'TimedOut', 'Sum'); + }); + +}); + +function verifyMetric(metric: Metric, metricName: string, statistic: string) { + expect(metric).toEqual({ + metricName, + namespace: 'AWS/States', + period: { + amount: 5, + unit: { + inMillis: 60000, + label: 'minutes', + }, + }, + statistic, + dimensions: { + Arn: 'resource', + }, + }); +} + +class FakeTask implements sfn.IStepFunctionsTask { + public bind(_task: sfn.Task): sfn.StepFunctionsTaskConfig { + return { + resourceArn: 'resource', + metricPrefixSingular: '', + metricPrefixPlural: '', + metricDimensions: { Arn: 'resource' }, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts new file mode 100644 index 0000000000000..94c67543e2e60 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/wait.test.ts @@ -0,0 +1,79 @@ +import '@aws-cdk/assert/jest'; +import * as cdk from '@aws-cdk/core'; +import { Pass, Wait, WaitTime } from '../lib'; +import { render } from './private/render-util'; + +describe('Wait State', () => { + test('wait time from ISO8601 timestamp', () => { + // GIVEN + const timestamp = '2025-01-01T00:00:00Z'; + + // WHEN + const waitTime = WaitTime.timestamp(timestamp); + + // THEN + expect(waitTime).toEqual({ + json: { + Timestamp: '2025-01-01T00:00:00Z', + }, + }); + }); + + test('wait time from seconds path in state object', () => { + // GIVEN + const secondsPath = '$.waitSeconds'; + + // WHEN + const waitTime = WaitTime.secondsPath(secondsPath); + + // THEN + expect(waitTime).toEqual({ + json: { + SecondsPath: '$.waitSeconds', + }, + }); + }); + + test('wait time from timestamp path in state object', () => { + // GIVEN + const path = '$.timestampPath'; + + // WHEN + const waitTime = WaitTime.timestampPath(path); + + // THEN + expect(waitTime).toEqual({ + json: { + TimestampPath: '$.timestampPath', + }, + }); + }); + + test('supports adding a next state', () => { + // GIVEN + const stack = new cdk.Stack(); + const chain = new Wait(stack, 'myWaitState', { + time: WaitTime.duration(cdk.Duration.seconds(30)), + }); + + // WHEN + chain.next(new Pass(stack, 'final pass', {})); + + // THEN + expect(render(stack, chain)).toEqual({ + StartAt: 'myWaitState', + States: { + 'final pass': { + End: true, + Type: 'Pass', + }, + 'myWaitState': { + Next: 'final pass', + Seconds: 30, + Type: 'Wait', + }, + }, + }); + }); + +}); \ No newline at end of file diff --git a/packages/@aws-cdk/cdk-assets-schema/lib/private/schema-helpers.ts b/packages/@aws-cdk/cdk-assets-schema/lib/private/schema-helpers.ts index a124b366a22ad..9ae1d1fcdb39a 100644 --- a/packages/@aws-cdk/cdk-assets-schema/lib/private/schema-helpers.ts +++ b/packages/@aws-cdk/cdk-assets-schema/lib/private/schema-helpers.ts @@ -1,3 +1,5 @@ +import { FileAssetPackaging } from '../file-asset'; + /** * Validate that a given key is of a given type in an object * @@ -51,4 +53,13 @@ export function isObjectAnd(p: (x: object) => A): (x: unknown) => A { export function assertIsObject(x: unknown): asserts x is object { if (typeof x !== 'object' || x === null) { throw new Error(`Expected a map, got '${x}'`); } -} \ No newline at end of file +} + +export function isFileAssetPackaging(x: unknown): FileAssetPackaging { + const str = isString(x); + const validValues = Object.values(FileAssetPackaging) as string[]; // Explicit cast needed because this is a string-valued enum + if (!validValues.includes(str)) { + throw new Error(`Expected a FileAssetPackaging (one of ${validValues.map(v => `'${v}'`).join(', ')}), got '${str}'`); + } + return x as any; +} diff --git a/packages/@aws-cdk/cdk-assets-schema/lib/validate.ts b/packages/@aws-cdk/cdk-assets-schema/lib/validate.ts index 041124f0b2061..2660a6adae98f 100644 --- a/packages/@aws-cdk/cdk-assets-schema/lib/validate.ts +++ b/packages/@aws-cdk/cdk-assets-schema/lib/validate.ts @@ -3,7 +3,7 @@ import { DockerImageAsset } from './docker-image-asset'; import { FileAsset } from './file-asset'; import { ManifestFile } from './manifest-schema'; import { loadMyPackageJson } from './private/my-package-json'; -import { assertIsObject, expectKey, isMapOf, isObjectAnd, isString } from './private/schema-helpers'; +import { assertIsObject, expectKey, isFileAssetPackaging, isMapOf, isObjectAnd, isString } from './private/schema-helpers'; const PACKAGE_VERSION = loadMyPackageJson().version; @@ -63,7 +63,7 @@ function isFileAsset(entry: object): FileAsset { expectKey(entry, 'source', source => { assertIsObject(source); expectKey(source, 'path', isString); - expectKey(source, 'packaging', isString, true); + expectKey(source, 'packaging', isFileAssetPackaging, true); return source; }); diff --git a/packages/@aws-cdk/cdk-assets-schema/test/validate.test.ts b/packages/@aws-cdk/cdk-assets-schema/test/validate.test.ts index 0f4c22e482e61..145ae265aec5f 100644 --- a/packages/@aws-cdk/cdk-assets-schema/test/validate.test.ts +++ b/packages/@aws-cdk/cdk-assets-schema/test/validate.test.ts @@ -1,81 +1,137 @@ -import { AssetManifestSchema } from '../lib'; +import { AssetManifestSchema, FileAssetPackaging } from '../lib'; -test('Correctly validate Docker image asset', () => { - expect(() => { - AssetManifestSchema.validate({ - version: AssetManifestSchema.currentVersion(), - dockerImages: { - asset: { - source: { - directory: '.', - }, - destinations: { - dest: { - region: 'us-north-20', - repositoryName: 'REPO', - imageTag: 'TAG', +describe('Docker image asset', () => { + test('valid input', () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + dockerImages: { + asset: { + source: { + directory: '.', + }, + destinations: { + dest: { + region: 'us-north-20', + repositoryName: 'REPO', + imageTag: 'TAG', + }, }, }, }, - }, - }); - }).not.toThrow(); -}); + }); + }).not.toThrow(); + }); -test('Throw on invalid Docker image asset', () => { - expect(() => { - AssetManifestSchema.validate({ - version: AssetManifestSchema.currentVersion(), - dockerImages: { - asset: { - source: { }, - destinations: { }, + test('invalid input', () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + dockerImages: { + asset: { + source: {}, + destinations: {}, + }, }, - }, - }); - }).toThrow(/dockerImages: source: Expected key 'directory' missing/); + }); + }).toThrow(/dockerImages: source: Expected key 'directory' missing/); + }); }); -test('Correctly validate File asset', () => { - expect(() => { - AssetManifestSchema.validate({ - version: AssetManifestSchema.currentVersion(), - files: { - asset: { - source: { - path: 'a/b/c', - }, - destinations: { - dest: { - region: 'us-north-20', - bucketName: 'Bouquet', - objectKey: 'key', +describe('File asset', () => { + describe('valid input', () => { + test('without packaging', () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + files: { + asset: { + source: { + path: 'a/b/c', + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + }, + }, }, }, - }, - }, + }); + }).not.toThrow(); }); - }).not.toThrow(); -}); -test('Throw on invalid file asset', () => { - expect(() => { - AssetManifestSchema.validate({ - version: AssetManifestSchema.currentVersion(), - files: { - asset: { - source: { - path: 3, + for (const packaging of Object.values(FileAssetPackaging)) { + test(`with "${packaging}" packaging`, () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + files: { + asset: { + source: { + path: 'a/b/c', + packaging, + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + }, + }, + }, + }, + }); + }).not.toThrow(); + }); + } + }); + + describe('invalid input', () => { + test('bad "source.path" property', () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + files: { + asset: { + source: { + path: 3, + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + }, + }, + }, }, - destinations: { - dest: { - region: 'us-north-20', - bucketName: 'Bouquet', - objectKey: 'key', + }); + }).toThrow(/Expected a string, got '3'/); + }); + + test('bad "source.packaging" property', () => { + expect(() => { + AssetManifestSchema.validate({ + version: AssetManifestSchema.currentVersion(), + files: { + asset: { + source: { + path: 'a/b/c', + packaging: 'BLACK_HOLE', + }, + destinations: { + dest: { + region: 'us-north-20', + bucketName: 'Bouquet', + objectKey: 'key', + }, + }, }, }, - }, - }, + }); + }).toThrow(/Expected a FileAssetPackaging \(one of [^)]+\), got 'BLACK_HOLE'/); }); - }).toThrow(/Expected a string, got '3'/); + }); }); diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index cfdb595fd50eb..e161d97d380c6 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,108 @@ +# CloudFormation Resource Specification v14.4.0 + +## New Resource Types + +* AWS::GlobalAccelerator::Accelerator +* AWS::GlobalAccelerator::EndpointGroup +* AWS::GlobalAccelerator::Listener +* AWS::ImageBuilder::Component +* AWS::ImageBuilder::DistributionConfiguration +* AWS::ImageBuilder::Image +* AWS::ImageBuilder::ImagePipeline +* AWS::ImageBuilder::ImageRecipe +* AWS::ImageBuilder::InfrastructureConfiguration +* AWS::Macie::CustomDataIdentifier +* AWS::Macie::FindingsFilter +* AWS::Macie::Session + +## Attribute Changes + +* AWS::Athena::NamedQuery NamedQueryId (__added__) +* AWS::SSM::Association AssociationId (__added__) + +## Property Changes + +* AWS::Cloud9::EnvironmentEC2 ConnectionType (__added__) +* AWS::CodeStarConnections::Connection Tags (__added__) +* AWS::DMS::Endpoint NeptuneSettings (__added__) +* AWS::DMS::ReplicationTask TaskData (__added__) +* AWS::ECS::Cluster ClusterSettings.DuplicatesAllowed (__deleted__) +* AWS::ECS::Cluster ClusterSettings.ItemType (__changed__) + * Old: ClusterSetting + * New: ClusterSettings +* AWS::ECS::Cluster Tags.DuplicatesAllowed (__deleted__) +* AWS::Neptune::DBCluster RestoreToTime (__added__) +* AWS::Neptune::DBCluster RestoreType (__added__) +* AWS::Neptune::DBCluster SourceDBClusterIdentifier (__added__) +* AWS::Neptune::DBCluster UseLatestRestorableTime (__added__) +* AWS::SSM::Association AutomationTargetParameterName (__added__) +* AWS::SSM::Association ComplianceSeverity (__added__) +* AWS::SSM::Association MaxConcurrency (__added__) +* AWS::SSM::Association MaxErrors (__added__) +* AWS::SSM::Association SyncCompliance (__added__) +* AWS::SSM::Association WaitForSuccessTimeoutSeconds (__added__) +* AWS::SSM::Association InstanceId.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::SSM::Association Name.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::SSM::Association Parameters.DuplicatesAllowed (__deleted__) +* AWS::SSM::Association Targets.DuplicatesAllowed (__deleted__) +* AWS::SSM::Association Targets.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::SSM::Parameter DataType (__added__) +* AWS::ServiceCatalog::CloudFormationProduct ReplaceProvisioningArtifacts (__added__) +* AWS::StepFunctions::StateMachine DefinitionS3Location (__added__) +* AWS::StepFunctions::StateMachine DefinitionSubstitutions (__added__) +* AWS::StepFunctions::StateMachine DefinitionString.Required (__changed__) + * Old: true + * New: false +* AWS::Synthetics::Canary RunConfig.Required (__changed__) + * Old: false + * New: true + +## Property Type Changes + +* AWS::EC2::LaunchTemplate.CapacityReservationPreference (__removed__) +* AWS::ECS::Cluster.ClusterSetting (__removed__) +* AWS::SSM::Association.ParameterValues (__removed__) +* AWS::DMS::Endpoint.NeptuneSettings (__added__) +* AWS::ECS::Cluster.ClusterSettings (__added__) +* AWS::StepFunctions::StateMachine.DefinitionSubstitutions (__added__) +* AWS::StepFunctions::StateMachine.S3Location (__added__) +* AWS::DLM::LifecyclePolicy.CreateRule CronExpression (__added__) +* AWS::DLM::LifecyclePolicy.CreateRule Interval.Required (__changed__) + * Old: true + * New: false +* AWS::DLM::LifecyclePolicy.CreateRule IntervalUnit.Required (__changed__) + * Old: true + * New: false +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRetainRule Interval.Required (__changed__) + * Old: false + * New: true +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRetainRule IntervalUnit.Required (__changed__) + * Old: false + * New: true +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRule Encrypted.Required (__changed__) + * Old: false + * New: true +* AWS::DLM::LifecyclePolicy.CrossRegionCopyRule TargetRegion.Required (__changed__) + * Old: false + * New: true +* AWS::EC2::LaunchTemplate.CapacityReservationSpecification CapacityReservationPreference.Type (__deleted__) +* AWS::EC2::LaunchTemplate.CapacityReservationSpecification CapacityReservationPreference.PrimitiveType (__added__) +* AWS::SSM::Association.S3OutputLocation OutputS3Region (__added__) +* AWS::SSM::Association.Target Key.UpdateType (__changed__) + * Old: Immutable + * New: Mutable +* AWS::SSM::Association.Target Values.DuplicatesAllowed (__deleted__) +* AWS::SSM::Association.Target Values.UpdateType (__changed__) + * Old: Immutable + * New: Mutable + + # CloudFormation Resource Specification v14.1.0 ## New Resource Types diff --git a/packages/@aws-cdk/cfnspec/cfn.version b/packages/@aws-cdk/cfnspec/cfn.version index 7b3b6e02bb3e9..72f51351fcd88 100644 --- a/packages/@aws-cdk/cfnspec/cfn.version +++ b/packages/@aws-cdk/cfnspec/cfn.version @@ -1 +1 @@ -14.1.0 +14.4.0 diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index d7e1771ee6a67..364df4c34f6dd 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -8696,16 +8696,22 @@ "AWS::DLM::LifecyclePolicy.CreateRule": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html", "Properties": { + "CronExpression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html#cfn-dlm-lifecyclepolicy-createrule-cronexpression", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Interval": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html#cfn-dlm-lifecyclepolicy-createrule-interval", "PrimitiveType": "Integer", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "IntervalUnit": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-createrule.html#cfn-dlm-lifecyclepolicy-createrule-intervalunit", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Times": { @@ -8723,13 +8729,13 @@ "Interval": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyretainrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyretainrule-interval", "PrimitiveType": "Integer", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "IntervalUnit": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyretainrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyretainrule-intervalunit", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -8752,7 +8758,7 @@ "Encrypted": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyrule-encrypted", "PrimitiveType": "Boolean", - "Required": false, + "Required": true, "UpdateType": "Mutable" }, "RetainRule": { @@ -8764,7 +8770,7 @@ "TargetRegion": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dlm-lifecyclepolicy-crossregioncopyrule.html#cfn-dlm-lifecyclepolicy-crossregioncopyrule-targetregion", "PrimitiveType": "String", - "Required": false, + "Required": true, "UpdateType": "Mutable" } } @@ -9078,6 +9084,53 @@ } } }, + "AWS::DMS::Endpoint.NeptuneSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html", + "Properties": { + "ErrorRetryDuration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-errorretryduration", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "IamAuthEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-iamauthenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxFileSize": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-maxfilesize", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxRetryCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-maxretrycount", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "S3BucketFolder": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-s3bucketfolder", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3BucketName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-s3bucketname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ServiceAccessRoleArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-neptunesettings.html#cfn-dms-endpoint-neptunesettings-serviceaccessrolearn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::DMS::Endpoint.S3Settings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-s3settings.html", "Properties": { @@ -10236,16 +10289,13 @@ } } }, - "AWS::EC2::LaunchTemplate.CapacityReservationPreference": { - "PrimitiveType": "String" - }, "AWS::EC2::LaunchTemplate.CapacityReservationSpecification": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-capacityreservationspecification.html", "Properties": { "CapacityReservationPreference": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-capacityreservationspecification.html#cfn-ec2-launchtemplate-launchtemplatedata-capacityreservationspecification-capacityreservationpreference", + "PrimitiveType": "String", "Required": false, - "Type": "CapacityReservationPreference", "UpdateType": "Mutable" }, "CapacityReservationTarget": { @@ -11693,19 +11743,19 @@ } } }, - "AWS::ECS::Cluster.ClusterSetting": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersetting.html", + "AWS::ECS::Cluster.ClusterSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersettings.html", "Properties": { "Name": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersetting.html#cfn-ecs-cluster-clustersetting-name", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersettings.html#cfn-ecs-cluster-clustersettings-name", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" }, "Value": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersetting.html#cfn-ecs-cluster-clustersetting-value", + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-clustersettings.html#cfn-ecs-cluster-clustersettings-value", "PrimitiveType": "String", - "Required": true, + "Required": false, "UpdateType": "Mutable" } } @@ -16403,6 +16453,46 @@ } } }, + "AWS::GlobalAccelerator::EndpointGroup.EndpointConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-endpointgroup-endpointconfiguration.html", + "Properties": { + "ClientIPPreservationEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-endpointgroup-endpointconfiguration.html#cfn-globalaccelerator-endpointgroup-endpointconfiguration-clientippreservationenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "EndpointId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-endpointgroup-endpointconfiguration.html#cfn-globalaccelerator-endpointgroup-endpointconfiguration-endpointid", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Weight": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-endpointgroup-endpointconfiguration.html#cfn-globalaccelerator-endpointgroup-endpointconfiguration-weight", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::GlobalAccelerator::Listener.PortRange": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-listener-portrange.html", + "Properties": { + "FromPort": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-listener-portrange.html#cfn-globalaccelerator-listener-portrange-fromport", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "ToPort": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-globalaccelerator-listener-portrange.html#cfn-globalaccelerator-listener-portrange-toport", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Glue::Classifier.CsvClassifier": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-glue-classifier-csvclassifier.html", "Properties": { @@ -18819,6 +18909,196 @@ } } }, + "AWS::ImageBuilder::DistributionConfiguration.Distribution": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-distribution.html", + "Properties": { + "AmiDistributionConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-distribution.html#cfn-imagebuilder-distributionconfiguration-distribution-amidistributionconfiguration", + "PrimitiveType": "Json", + "Required": false, + "UpdateType": "Mutable" + }, + "LicenseConfigurationArns": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-distribution.html#cfn-imagebuilder-distributionconfiguration-distribution-licenseconfigurationarns", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Region": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-distributionconfiguration-distribution.html#cfn-imagebuilder-distributionconfiguration-distribution-region", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::Image.ImageTestsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-image-imagetestsconfiguration.html", + "Properties": { + "ImageTestsEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-image-imagetestsconfiguration.html#cfn-imagebuilder-image-imagetestsconfiguration-imagetestsenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "TimeoutMinutes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-image-imagetestsconfiguration.html#cfn-imagebuilder-image-imagetestsconfiguration-timeoutminutes", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::ImagePipeline.ImageTestsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-imagetestsconfiguration.html", + "Properties": { + "ImageTestsEnabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-imagetestsconfiguration.html#cfn-imagebuilder-imagepipeline-imagetestsconfiguration-imagetestsenabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "TimeoutMinutes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-imagetestsconfiguration.html#cfn-imagebuilder-imagepipeline-imagetestsconfiguration-timeoutminutes", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::ImagePipeline.Schedule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html", + "Properties": { + "PipelineExecutionStartCondition": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html#cfn-imagebuilder-imagepipeline-schedule-pipelineexecutionstartcondition", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ScheduleExpression": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagepipeline-schedule.html#cfn-imagebuilder-imagepipeline-schedule-scheduleexpression", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::ImageRecipe.ComponentConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-componentconfiguration.html", + "Properties": { + "ComponentArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-componentconfiguration.html#cfn-imagebuilder-imagerecipe-componentconfiguration-componentarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::ImageRecipe.EbsInstanceBlockDeviceSpecification": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html", + "Properties": { + "DeleteOnTermination": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-deleteontermination", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "Encrypted": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-encrypted", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "Iops": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-iops", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "SnapshotId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-snapshotid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "VolumeSize": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-volumesize", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "VolumeType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification.html#cfn-imagebuilder-imagerecipe-ebsinstanceblockdevicespecification-volumetype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::ImageRecipe.InstanceBlockDeviceMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-instanceblockdevicemapping.html", + "Properties": { + "DeviceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-instanceblockdevicemapping.html#cfn-imagebuilder-imagerecipe-instanceblockdevicemapping-devicename", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Ebs": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-instanceblockdevicemapping.html#cfn-imagebuilder-imagerecipe-instanceblockdevicemapping-ebs", + "Required": false, + "Type": "EbsInstanceBlockDeviceSpecification", + "UpdateType": "Immutable" + }, + "NoDevice": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-instanceblockdevicemapping.html#cfn-imagebuilder-imagerecipe-instanceblockdevicemapping-nodevice", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "VirtualName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-imagerecipe-instanceblockdevicemapping.html#cfn-imagebuilder-imagerecipe-instanceblockdevicemapping-virtualname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::InfrastructureConfiguration.Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-logging.html", + "Properties": { + "S3Logs": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-logging.html#cfn-imagebuilder-infrastructureconfiguration-logging-s3logs", + "Required": false, + "Type": "S3Logs", + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::InfrastructureConfiguration.S3Logs": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-s3logs.html", + "Properties": { + "S3BucketName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-s3logs.html#cfn-imagebuilder-infrastructureconfiguration-s3logs-s3bucketname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "S3KeyPrefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-imagebuilder-infrastructureconfiguration-s3logs.html#cfn-imagebuilder-infrastructureconfiguration-s3logs-s3keyprefix", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::IoT1Click::Project.DeviceTemplate": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iot1click-project-devicetemplate.html", "Properties": { @@ -23716,6 +23996,20 @@ } } }, + "AWS::Macie::FindingsFilter.Criterion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-macie-findingsfilter-criterion.html" + }, + "AWS::Macie::FindingsFilter.FindingCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-macie-findingsfilter-findingcriteria.html", + "Properties": { + "Criterion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-macie-findingsfilter-findingcriteria.html#cfn-macie-findingsfilter-findingcriteria-criterion", + "Required": false, + "Type": "Criterion", + "UpdateType": "Mutable" + } + } + }, "AWS::ManagedBlockchain::Member.ApprovalThresholdPolicy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-managedblockchain-member-approvalthresholdpolicy.html", "Properties": { @@ -28499,19 +28793,6 @@ } } }, - "AWS::SSM::Association.ParameterValues": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-parametervalues.html", - "Properties": { - "ParameterValues": { - "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-parametervalues.html#cfn-ssm-association-parametervalues-parametervalues", - "DuplicatesAllowed": false, - "PrimitiveItemType": "String", - "Required": true, - "Type": "List", - "UpdateType": "Mutable" - } - } - }, "AWS::SSM::Association.S3OutputLocation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-s3outputlocation.html", "Properties": { @@ -28526,6 +28807,12 @@ "PrimitiveType": "String", "Required": false, "UpdateType": "Mutable" + }, + "OutputS3Region": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-s3outputlocation.html#cfn-ssm-association-s3outputlocation-outputs3region", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -28536,15 +28823,14 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-target.html#cfn-ssm-association-target-key", "PrimitiveType": "String", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "Values": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ssm-association-target.html#cfn-ssm-association-target-values", - "DuplicatesAllowed": false, "PrimitiveItemType": "String", "Required": true, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, @@ -29422,6 +29708,9 @@ } } }, + "AWS::StepFunctions::StateMachine.DefinitionSubstitutions": { + "PrimitiveType": "Json" + }, "AWS::StepFunctions::StateMachine.LogDestination": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-logdestination.html", "Properties": { @@ -29457,6 +29746,29 @@ } } }, + "AWS::StepFunctions::StateMachine.S3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-s3location.html", + "Properties": { + "Bucket": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-s3location.html#cfn-stepfunctions-statemachine-s3location-bucket", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-s3location.html#cfn-stepfunctions-statemachine-s3location-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Version": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-s3location.html#cfn-stepfunctions-statemachine-s3location-version", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::StepFunctions::StateMachine.TagsEntry": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stepfunctions-statemachine-tagsentry.html", "Properties": { @@ -31602,7 +31914,7 @@ } } }, - "ResourceSpecificationVersion": "14.1.0", + "ResourceSpecificationVersion": "14.4.0", "ResourceTypes": { "AWS::ACMPCA::Certificate": { "Attributes": { @@ -34993,6 +35305,11 @@ } }, "AWS::Athena::NamedQuery": { + "Attributes": { + "NamedQueryId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-namedquery.html", "Properties": { "Database": { @@ -35967,6 +36284,12 @@ "Required": false, "UpdateType": "Immutable" }, + "ConnectionType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloud9-environmentec2.html#cfn-cloud9-environmentec2-connectiontype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "Description": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloud9-environmentec2.html#cfn-cloud9-environmentec2-description", "PrimitiveType": "String", @@ -37238,6 +37561,13 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html#cfn-codestarconnections-connection-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" } } }, @@ -38557,6 +38887,12 @@ "Type": "MongoDbSettings", "UpdateType": "Mutable" }, + "NeptuneSettings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dms-endpoint.html#cfn-dms-endpoint-neptunesettings", + "Required": false, + "Type": "NeptuneSettings", + "UpdateType": "Mutable" + }, "Password": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dms-endpoint.html#cfn-dms-endpoint-password", "PrimitiveType": "String", @@ -38853,6 +39189,12 @@ "PrimitiveType": "String", "Required": true, "UpdateType": "Immutable" + }, + "TaskData": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dms-replicationtask.html#cfn-dms-replicationtask-taskdata", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -41921,15 +42263,13 @@ }, "ClusterSettings": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-clustersettings", - "DuplicatesAllowed": false, - "ItemType": "ClusterSetting", + "ItemType": "ClusterSettings", "Required": false, "Type": "List", "UpdateType": "Mutable" }, "Tags": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-tags", - "DuplicatesAllowed": true, "ItemType": "Tag", "Required": false, "Type": "List", @@ -44812,6 +45152,151 @@ } } }, + "AWS::GlobalAccelerator::Accelerator": { + "Attributes": { + "AcceleratorArn": { + "PrimitiveType": "String" + }, + "DnsName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html", + "Properties": { + "Enabled": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html#cfn-globalaccelerator-accelerator-enabled", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "IpAddressType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html#cfn-globalaccelerator-accelerator-ipaddresstype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "IpAddresses": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html#cfn-globalaccelerator-accelerator-ipaddresses", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html#cfn-globalaccelerator-accelerator-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html#cfn-globalaccelerator-accelerator-tags", + "ItemType": "Tag", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::GlobalAccelerator::EndpointGroup": { + "Attributes": { + "EndpointGroupArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html", + "Properties": { + "EndpointConfigurations": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-endpointconfigurations", + "ItemType": "EndpointConfiguration", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "EndpointGroupRegion": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-endpointgroupregion", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "HealthCheckIntervalSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-healthcheckintervalseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "HealthCheckPath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-healthcheckpath", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "HealthCheckPort": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-healthcheckport", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "HealthCheckProtocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-healthcheckprotocol", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ListenerArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-listenerarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ThresholdCount": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-thresholdcount", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "TrafficDialPercentage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html#cfn-globalaccelerator-endpointgroup-trafficdialpercentage", + "PrimitiveType": "Double", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::GlobalAccelerator::Listener": { + "Attributes": { + "ListenerArn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html", + "Properties": { + "AcceleratorArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html#cfn-globalaccelerator-listener-acceleratorarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ClientAffinity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html#cfn-globalaccelerator-listener-clientaffinity", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "PortRanges": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html#cfn-globalaccelerator-listener-portranges", + "ItemType": "PortRange", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html#cfn-globalaccelerator-listener-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, "AWS::Glue::Classifier": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-classifier.html", "Properties": { @@ -46471,6 +46956,360 @@ } } }, + "AWS::ImageBuilder::Component": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "Encrypted": { + "PrimitiveType": "Boolean" + }, + "Type": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html", + "Properties": { + "ChangeDescription": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-changedescription", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Data": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-data", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "KmsKeyId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-kmskeyid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Platform": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-platform", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + }, + "Uri": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-uri", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Version": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html#cfn-imagebuilder-component-version", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::DistributionConfiguration": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html#cfn-imagebuilder-distributionconfiguration-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Distributions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html#cfn-imagebuilder-distributionconfiguration-distributions", + "ItemType": "Distribution", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html#cfn-imagebuilder-distributionconfiguration-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html#cfn-imagebuilder-distributionconfiguration-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::Image": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "ImageId": { + "PrimitiveType": "String" + }, + "OutputResources": { + "Type": "OutputResources" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html", + "Properties": { + "DistributionConfigurationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html#cfn-imagebuilder-image-distributionconfigurationarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "ImageRecipeArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html#cfn-imagebuilder-image-imagerecipearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ImageTestsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html#cfn-imagebuilder-image-imagetestsconfiguration", + "Required": false, + "Type": "ImageTestsConfiguration", + "UpdateType": "Immutable" + }, + "InfrastructureConfigurationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html#cfn-imagebuilder-image-infrastructureconfigurationarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html#cfn-imagebuilder-image-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::ImagePipeline": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "DistributionConfigurationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-distributionconfigurationarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ImageRecipeArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-imagerecipearn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "ImageTestsConfiguration": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-imagetestsconfiguration", + "Required": false, + "Type": "ImageTestsConfiguration", + "UpdateType": "Mutable" + }, + "InfrastructureConfigurationArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-infrastructureconfigurationarn", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Schedule": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-schedule", + "Required": false, + "Type": "Schedule", + "UpdateType": "Mutable" + }, + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-status", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html#cfn-imagebuilder-imagepipeline-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + } + } + }, + "AWS::ImageBuilder::ImageRecipe": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html", + "Properties": { + "BlockDeviceMappings": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-blockdevicemappings", + "ItemType": "InstanceBlockDeviceMapping", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "Components": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-components", + "ItemType": "ComponentConfiguration", + "Required": true, + "Type": "List", + "UpdateType": "Immutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "ParentImage": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-parentimage", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Immutable" + }, + "Version": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html#cfn-imagebuilder-imagerecipe-version", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::ImageBuilder::InfrastructureConfiguration": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "InstanceProfileName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-instanceprofilename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "InstanceTypes": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-instancetypes", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "KeyPair": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-keypair", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-logging", + "PrimitiveType": "Json", + "Required": false, + "Type": "Logging", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "SecurityGroupIds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-securitygroupids", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "SnsTopicArn": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-snstopicarn", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SubnetId": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-subnetid", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-tags", + "PrimitiveItemType": "String", + "Required": false, + "Type": "Map", + "UpdateType": "Mutable" + }, + "TerminateInstanceOnFailure": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html#cfn-imagebuilder-infrastructureconfiguration-terminateinstanceonfailure", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Inspector::AssessmentTarget": { "Attributes": { "Arn": { @@ -48018,6 +48857,135 @@ } } }, + "AWS::Macie::CustomDataIdentifier": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "CreatedAt": { + "PrimitiveType": "String" + }, + "Deleted": { + "PrimitiveType": "Boolean" + }, + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html", + "Properties": { + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "IgnoreWords": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-ignorewords", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "Keywords": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-keywords", + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Immutable" + }, + "MaximumMatchDistance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-maximummatchdistance", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Regex": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html#cfn-macie-customdataidentifier-regex", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::Macie::FindingsFilter": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "FindingsFilterListItems": { + "ItemType": "FindingsFilterListItem", + "Type": "List" + }, + "Id": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html#cfn-macie-findingsfilter-action", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Description": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html#cfn-macie-findingsfilter-description", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "FindingCriteria": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html#cfn-macie-findingsfilter-findingcriteria", + "Required": true, + "Type": "FindingCriteria", + "UpdateType": "Mutable" + }, + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html#cfn-macie-findingsfilter-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Position": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html#cfn-macie-findingsfilter-position", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::Macie::Session": { + "Attributes": { + "AwsAccountId": { + "PrimitiveType": "String" + }, + "ServiceRole": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-session.html", + "Properties": { + "FindingPublishingFrequency": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-session.html#cfn-macie-session-findingpublishingfrequency", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Status": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-session.html#cfn-macie-session-status", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::ManagedBlockchain::Member": { "Attributes": { "MemberId": { @@ -48572,12 +49540,30 @@ "Required": false, "UpdateType": "Mutable" }, + "RestoreToTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-restoretotime", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "RestoreType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-restoretype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "SnapshotIdentifier": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-snapshotidentifier", "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" }, + "SourceDBClusterIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-sourcedbclusteridentifier", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "StorageEncrypted": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-storageencrypted", "PrimitiveType": "Boolean", @@ -48591,6 +49577,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "UseLatestRestorableTime": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-uselatestrestorabletime", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, "VpcSecurityGroupIds": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-neptune-dbcluster.html#cfn-neptune-dbcluster-vpcsecuritygroupids", "PrimitiveItemType": "String", @@ -52994,6 +53986,11 @@ } }, "AWS::SSM::Association": { + "Attributes": { + "AssociationId": { + "PrimitiveType": "String" + } + }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html", "Properties": { "AssociationName": { @@ -53002,6 +53999,18 @@ "Required": false, "UpdateType": "Mutable" }, + "AutomationTargetParameterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-automationtargetparametername", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "ComplianceSeverity": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-complianceseverity", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "DocumentVersion": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-documentversion", "PrimitiveType": "String", @@ -53012,13 +54021,25 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-instanceid", "PrimitiveType": "String", "Required": false, - "UpdateType": "Immutable" + "UpdateType": "Mutable" + }, + "MaxConcurrency": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-maxconcurrency", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "MaxErrors": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-maxerrors", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" }, "Name": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-name", "PrimitiveType": "String", "Required": true, - "UpdateType": "Immutable" + "UpdateType": "Mutable" }, "OutputLocation": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-outputlocation", @@ -53028,7 +54049,6 @@ }, "Parameters": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-parameters", - "DuplicatesAllowed": false, "ItemType": "ParameterValues", "Required": false, "Type": "Map", @@ -53040,13 +54060,24 @@ "Required": false, "UpdateType": "Mutable" }, + "SyncCompliance": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-synccompliance", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Targets": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-targets", - "DuplicatesAllowed": false, "ItemType": "Target", "Required": false, "Type": "List", - "UpdateType": "Immutable" + "UpdateType": "Mutable" + }, + "WaitForSuccessTimeoutSeconds": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html#cfn-ssm-association-waitforsuccesstimeoutseconds", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" } } }, @@ -53290,6 +54321,12 @@ "Required": false, "UpdateType": "Mutable" }, + "DataType": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-parameter.html#cfn-ssm-parameter-datatype", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, "Description": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-parameter.html#cfn-ssm-parameter-description", "PrimitiveType": "String", @@ -53983,6 +55020,12 @@ "Type": "List", "UpdateType": "Mutable" }, + "ReplaceProvisioningArtifacts": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-cloudformationproduct.html#cfn-servicecatalog-cloudformationproduct-replaceprovisioningartifacts", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, "SupportDescription": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-cloudformationproduct.html#cfn-servicecatalog-cloudformationproduct-supportdescription", "PrimitiveType": "String", @@ -54649,10 +55692,22 @@ }, "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html", "Properties": { + "DefinitionS3Location": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#cfn-stepfunctions-statemachine-definitions3location", + "Required": false, + "Type": "S3Location", + "UpdateType": "Mutable" + }, "DefinitionString": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#cfn-stepfunctions-statemachine-definitionstring", "PrimitiveType": "String", - "Required": true, + "Required": false, + "UpdateType": "Mutable" + }, + "DefinitionSubstitutions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html#cfn-stepfunctions-statemachine-definitionsubstitutions", + "Required": false, + "Type": "DefinitionSubstitutions", "UpdateType": "Mutable" }, "LoggingConfiguration": { @@ -54731,7 +55786,7 @@ }, "RunConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-runconfig", - "Required": false, + "Required": true, "Type": "RunConfig", "UpdateType": "Mutable" }, diff --git a/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json b/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json new file mode 100644 index 0000000000000..0059cd2577767 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/540_SSM_Association_Parameters_patch.json @@ -0,0 +1,20 @@ +{ + "ResourceTypes": { + "AWS::SSM::Association": { + "patch": { + "description": "Removes 'ItemType' property since 'ParameterValues' is (currently) not defined in the spec and the documentation states it to be a list of String", + "operations": [ + { + "op": "remove", + "path": "/Properties/Parameters/ItemType" + }, + { + "op": "add", + "path": "/Properties/Parameters/PrimitiveItemType", + "value": "String" + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json b/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json new file mode 100644 index 0000000000000..858350a6c2bc5 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/550_ImageBuilder_Image_Attributes_OutputResources_patch.json @@ -0,0 +1,21 @@ +{ + "ResourceTypes": { + "AWS::ImageBuilder::Image": { + "patch": { + "description": "Replaces 'OutputResources' attribute type to be an array of Strings as it is (currently) not defined in the spec", + "operations": [ + { + "op": "replace", + "path": "/Attributes/OutputResources/Type", + "value": "List" + }, + { + "op": "add", + "path": "/Attributes/OutputResources/PrimitiveItemType", + "value": "String" + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cfnspec/spec-source/560_Macie_FindingsFilter_Attributes_FindingsFilterListItems_patch.json b/packages/@aws-cdk/cfnspec/spec-source/560_Macie_FindingsFilter_Attributes_FindingsFilterListItems_patch.json new file mode 100644 index 0000000000000..035ce76d073fb --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/560_Macie_FindingsFilter_Attributes_FindingsFilterListItems_patch.json @@ -0,0 +1,20 @@ +{ + "ResourceTypes": { + "AWS::Macie::FindingsFilter": { + "patch": { + "description": "Replaces 'FindingsFilterListItems' attribute to be an array of JSON values as it is (currently) not defined in the spec", + "operations": [ + { + "op": "remove", + "path": "/Attributes/FindingsFilterListItems/ItemType" + }, + { + "op": "add", + "path": "/Attributes/FindingsFilterListItems/PrimitiveItemType", + "value": "Json" + } + ] + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 71119c75b1020..5389145c4941a 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -117,10 +117,12 @@ "@aws-cdk/aws-fms": "0.0.0", "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-guardduty": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", @@ -134,6 +136,7 @@ "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconvert": "0.0.0", "@aws-cdk/aws-medialive": "0.0.0", @@ -236,10 +239,12 @@ "@aws-cdk/aws-fms": "0.0.0", "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-guardduty": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", @@ -253,6 +258,7 @@ "@aws-cdk/aws-lakeformation": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconvert": "0.0.0", "@aws-cdk/aws-medialive": "0.0.0", diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 5a9315a0850ca..341943a748bca 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -91,7 +91,7 @@ export class Construct extends constructs.Construct implements IConstruct { * This method can be implemented by derived constructs in order to perform * validation logic. It is called on all constructs before synthesis. * - * @returns An array of validation error messages, or an empty array if there the construct is valid. + * @returns An array of validation error messages, or an empty array if the construct is valid. */ protected onValidate(): string[] { return this.validate(); @@ -132,7 +132,7 @@ export class Construct extends constructs.Construct implements IConstruct { * This method can be implemented by derived constructs in order to perform * validation logic. It is called on all constructs before synthesis. * - * @returns An array of validation error messages, or an empty array if there the construct is valid. + * @returns An array of validation error messages, or an empty array if the construct is valid. */ protected validate(): string[] { return []; diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts new file mode 100644 index 0000000000000..a1149f91e4990 --- /dev/null +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/bootstrapless-synthesizer.ts @@ -0,0 +1,60 @@ +import { DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource } from '../assets'; +import { ISynthesisSession } from '../construct-compat'; +import { addStackArtifactToAssembly, assertBound } from './_shared'; +import { DefaultStackSynthesizer } from './default-synthesizer'; + +/** + * Construction properties of {@link BootstraplessSynthesizer}. + */ +export interface BootstraplessSynthesizerProps { + /** + * The deploy Role ARN to use. + * + * @default - No deploy role (use CLI credentials) + * + */ + readonly deployRoleArn?: string; + + /** + * The CFN execution Role ARN to use. + * + * @default - No CloudFormation role (use CLI credentials) + */ + readonly cloudFormationExecutionRoleArn?: string; +} + +/** + * A special synthesizer that behaves similarly to DefaultStackSynthesizer, + * but doesn't require bootstrapping the environment it operates in. + * Because of that, stacks using it cannot have assets inside of them. + * Used by the CodePipeline construct for the support stacks needed for + * cross-region replication S3 buckets. + */ +export class BootstraplessSynthesizer extends DefaultStackSynthesizer { + constructor(props: BootstraplessSynthesizerProps) { + super({ + deployRoleArn: props.deployRoleArn, + cloudFormationExecutionRole: props.cloudFormationExecutionRoleArn, + }); + } + + public addFileAsset(_asset: FileAssetSource): FileAssetLocation { + throw new Error('Cannot add assets to a Stack that uses the BootstraplessSynthesizer'); + } + + public addDockerImageAsset(_asset: DockerImageAssetSource): DockerImageAssetLocation { + throw new Error('Cannot add assets to a Stack that uses the BootstraplessSynthesizer'); + } + + public synthesizeStackArtifacts(session: ISynthesisSession): void { + assertBound(this.stack); + + // do _not_ treat the template as an asset, + // because this synthesizer doesn't have a bootstrap bucket to put it in + addStackArtifactToAssembly(session, this.stack, { + assumeRoleArn: this.deployRoleArn, + cloudFormationExecutionRoleArn: this.cloudFormationExecutionRoleArn, + requiresBootstrapStackVersion: 1, + }, []); + } +} diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts index 13b65a5d8613c..ace086a9c4bd3 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/default-synthesizer.ts @@ -140,11 +140,11 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { */ public static readonly DEFAULT_FILE_ASSETS_BUCKET_NAME = 'cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region}'; - private stack?: Stack; + private _stack?: Stack; private bucketName?: string; private repositoryName?: string; - private deployRoleArn?: string; - private cloudFormationExecutionRoleArn?: string; + private _deployRoleArn?: string; + private _cloudFormationExecutionRoleArn?: string; private assetPublishingRoleArn?: string; private readonly files: NonNullable = {}; @@ -154,7 +154,7 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { } public bind(stack: Stack): void { - this.stack = stack; + this._stack = stack; const qualifier = this.props.qualifier ?? stack.node.tryGetContext(BOOTSTRAP_QUALIFIER_CONTEXT) ?? DefaultStackSynthesizer.DEFAULT_QUALIFIER; @@ -176,8 +176,8 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { // tslint:disable:max-line-length this.bucketName = specialize(this.props.fileAssetsBucketName ?? DefaultStackSynthesizer.DEFAULT_FILE_ASSETS_BUCKET_NAME); this.repositoryName = specialize(this.props.imageAssetsRepositoryName ?? DefaultStackSynthesizer.DEFAULT_IMAGE_ASSETS_REPOSITORY_NAME); - this.deployRoleArn = specialize(this.props.deployRoleArn ?? DefaultStackSynthesizer.DEFAULT_DEPLOY_ROLE_ARN); - this.cloudFormationExecutionRoleArn = specialize(this.props.cloudFormationExecutionRole ?? DefaultStackSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN); + this._deployRoleArn = specialize(this.props.deployRoleArn ?? DefaultStackSynthesizer.DEFAULT_DEPLOY_ROLE_ARN); + this._cloudFormationExecutionRoleArn = specialize(this.props.cloudFormationExecutionRole ?? DefaultStackSynthesizer.DEFAULT_CLOUDFORMATION_ROLE_ARN); this.assetPublishingRoleArn = specialize(this.props.assetPublishingRoleArn ?? DefaultStackSynthesizer.DEFAULT_ASSET_PUBLISHING_ROLE_ARN); // tslint:enable:max-line-length } @@ -259,13 +259,37 @@ export class DefaultStackSynthesizer implements IStackSynthesizer { const artifactId = this.writeAssetManifest(session); addStackArtifactToAssembly(session, this.stack, { - assumeRoleArn: this.deployRoleArn, - cloudFormationExecutionRoleArn: this.cloudFormationExecutionRoleArn, + assumeRoleArn: this._deployRoleArn, + cloudFormationExecutionRoleArn: this._cloudFormationExecutionRoleArn, stackTemplateAssetObjectUrl: templateManifestUrl, requiresBootstrapStackVersion: 1, }, [artifactId]); } + /** + * Returns the ARN of the deploy Role. + */ + public get deployRoleArn(): string { + if (!this._deployRoleArn) { + throw new Error('deployRoleArn getter can only be called after the synthesizer has been bound to a Stack'); + } + return this._deployRoleArn; + } + + /** + * Returns the ARN of the CFN execution Role. + */ + public get cloudFormationExecutionRoleArn(): string { + if (!this._cloudFormationExecutionRoleArn) { + throw new Error('cloudFormationExecutionRoleArn getter can only be called after the synthesizer has been bound to a Stack'); + } + return this._cloudFormationExecutionRoleArn; + } + + protected get stack(): Stack | undefined { + return this._stack; + } + /** * Add the stack's template as one of the manifest assets * diff --git a/packages/@aws-cdk/core/lib/stack-synthesizers/index.ts b/packages/@aws-cdk/core/lib/stack-synthesizers/index.ts index 5920f19bae2c9..b4ad67384729d 100644 --- a/packages/@aws-cdk/core/lib/stack-synthesizers/index.ts +++ b/packages/@aws-cdk/core/lib/stack-synthesizers/index.ts @@ -1,4 +1,5 @@ export * from './types'; export * from './default-synthesizer'; export * from './legacy'; -export * from './nested'; \ No newline at end of file +export * from './bootstrapless-synthesizer'; +export * from './nested'; diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index c32f75234584e..37c5a0887d52c 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -21,6 +21,19 @@ import * as version from '../lib/version'; // tslint:disable:no-shadowed-variable max-line-length async function parseCommandLineArguments() { + // Use the following configuration for array arguments: + // + // { type: 'array', default: [], nargs: 1, requiresArg: true } + // + // The default behavior of yargs is to eat all strings following an array argument: + // + // ./prog --arg one two positional => will parse to { arg: ['one', 'two', 'positional'], _: [] } (so no positional arguments) + // ./prog --arg one two -- positional => does not help, for reasons that I can't understand. Still gets parsed incorrectly. + // + // By using the config above, every --arg will only consume one argument, so you can do the following: + // + // ./prog --arg one --arg two position => will parse to { arg: ['one', 'two'], _: ['positional'] }. + const initTemplateLanuages = await availableInitLanguages; return yargs .env('CDK') @@ -56,8 +69,8 @@ async function parseCommandLineArguments() { .option('qualifier', { type: 'string', desc: 'Unique string to distinguish multiple bootstrap stacks', default: undefined }) .option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] }) .option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true}) - .option('trust', { type: 'array', desc: 'The (space-separated) list of AWS account IDs that should be trusted to perform deployments into this environment', default: [], hidden: true }) - .option('cloudformation-execution-policies', { type: 'array', desc: 'The (space-separated) list of Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed', default: [], hidden: true }) + .option('trust', { type: 'array', desc: 'The AWS account IDs that should be trusted to perform deployments into this environment (may be repeated)', default: [], nargs: 1, requiresArg: true, hidden: true }) + .option('cloudformation-execution-policies', { type: 'array', desc: 'The Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed (may be repeated)', default: [], nargs: 1, requiresArg: true, hidden: true }) .option('force', { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false }), ) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs @@ -296,6 +309,8 @@ initCommandLine() }) .catch(err => { error(err.message); - debug(err.stack); + if (err.stack) { + debug(err.stack); + } process.exitCode = 1; }); diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index 98c2e03ac1a62..499a5af46beb0 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -127,7 +127,7 @@ export class SdkProvider { * If `region` is undefined, the default value will be used. */ public async withAssumedRole(roleArn: string, externalId: string | undefined, region: string | undefined) { - debug(`Assuming role '${roleArn}'`); + debug(`Assuming role '${roleArn}'.`); region = region ?? this.defaultRegion; const creds = new AWS.ChainableTemporaryCredentials({ diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index d84b14627cba4..239f85fef51bc 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -64,27 +64,27 @@ export class SDK implements ISDK { } public cloudFormation(): AWS.CloudFormation { - return new AWS.CloudFormation(this.config); + return wrapServiceErrorHandling(new AWS.CloudFormation(this.config)); } public ec2(): AWS.EC2 { - return new AWS.EC2(this.config); + return wrapServiceErrorHandling(new AWS.EC2(this.config)); } public ssm(): AWS.SSM { - return new AWS.SSM(this.config); + return wrapServiceErrorHandling(new AWS.SSM(this.config)); } public s3(): AWS.S3 { - return new AWS.S3(this.config); + return wrapServiceErrorHandling(new AWS.S3(this.config)); } public route53(): AWS.Route53 { - return new AWS.Route53(this.config); + return wrapServiceErrorHandling(new AWS.Route53(this.config)); } public ecr(): AWS.ECR { - return new AWS.ECR(this.config); + return wrapServiceErrorHandling(new AWS.ECR(this.config)); } public async currentAccount(): Promise { @@ -103,4 +103,113 @@ export class SDK implements ISDK { } } +/** + * Return a wrapping object for the underlying service object + * + * Responds to failures in the underlying service calls, in two different + * ways: + * + * - When errors are encountered, log the failing call and the error that + * it triggered (at debug level). This is necessary because the lack of + * stack traces in NodeJS otherwise makes it very hard to suss out where + * a certain AWS error occurred. + * - The JS SDK has a funny business of wrapping any credential-based error + * in a super-generic (and in our case wrong) exception. If we then use a + * 'ChainableTemporaryCredentials' and the target role doesn't exist, + * the error message that shows up by default is super misleading + * (https://github.com/aws/aws-sdk-js/issues/3272). We can fix this because + * the exception contains the "inner exception", so we unwrap and throw + * the correct error ("cannot assume role"). + * + * The wrapping business below is slightly more complicated than you'd think + * because we must hook into the `promise()` method of the object that's being + * returned from the methods of the object that we wrap, so there's two + * levels of wrapping going on, and also some exceptions to the wrapping magic. + */ +function wrapServiceErrorHandling(serviceObject: A): A { + const classObject = serviceObject.constructor.prototype; + + return new Proxy(serviceObject, { + get(obj: A, prop: string) { + const real = (obj as any)[prop]; + // Things we don't want to intercept: + // - Anything that's not a function. + // - 'constructor', s3.upload() will use this to do some magic and we need the underlying constructor. + // - Any method that's not on the service class (do not intercept 'makeRequest' and other helpers). + if (prop === 'constructor' || !classObject.hasOwnProperty(prop) || !isFunction(real)) { return real; } + + // NOTE: This must be a function() and not an () => { + // because I need 'this' to be dynamically bound and not statically bound. + // If your linter complains don't listen to it! + return function(this: any) { + // Call the underlying function. If it returns an object with a promise() + // method on it, wrap that 'promise' method. + const args = [].slice.call(arguments, 0); + const response = real.apply(this, args); + + // Don't intercept unless the return value is an object with a '.promise()' method. + if (typeof response !== 'object' || !response) { return response; } + if (!('promise' in response)) { return response; } + + // Return an object with the promise method replaced with a wrapper which will + // do additional things to errors. + return Object.assign(Object.create(response), { + promise() { + return response.promise().catch((e: Error) => { + e = makeDetailedException(e); + debug(`Call failed: ${prop}(${JSON.stringify(args[0])}) => ${e.message}`); + return Promise.reject(e); // Re-'throw' the new error + }); + }, + }); + }; + }, + }); +} + const CURRENT_ACCOUNT_KEY = Symbol('current_account_key'); + +function isFunction(x: any): x is (...args: any[]) => any { + return x && {}.toString.call(x) === '[object Function]'; +} + +/** + * Extract a more detailed error out of a generic error if we can + */ +function makeDetailedException(e: Error): Error { + // This is the super-generic "something's wrong" error that the JS SDK wraps other errors in. + // https://github.com/aws/aws-sdk-js/blob/f0ac2e53457c7512883d0677013eacaad6cd8a19/lib/event_listeners.js#L84 + if (typeof e.message === 'string' && e.message.startsWith('Missing credentials in config')) { + const original = (e as any).originalError; + if (original) { + // When the SDK does a 'util.copy', they lose the Error-ness of the inner error + // (they copy the Error's properties into a plain object) so make it an Error object again. + e = Object.assign(new Error(), original); + } + } + + // At this point, the error might still be a generic "ChainableTemporaryCredentials failed" + // error which wraps the REAL error (AssumeRole failed). We're going to replace the error + // message with one that's more likely to help users, and tell them the most probable + // fix (bootstrapping). The underlying service call failure will be appended below. + if (e.message === 'Could not load credentials from ChainableTemporaryCredentials') { + e.message = 'Could not assume role in target account (did you bootstrap the environment with the right \'--trust\'s?)'; + } + + // Replace the message on this error with a concatenation of all inner error messages. + // Must more clear what's going on that way. + e.message = allChainedExceptionMessages(e); + return e; +} + +/** + * Return the concatenated message of all exceptions in the AWS exception chain + */ +function allChainedExceptionMessages(e: Error | undefined) { + const ret = new Array(); + while (e) { + ret.push(e.message); + e = (e as any).originalError; + } + return ret.join(': '); +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index db4d75f86fc38..93ddb1264f2ff 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -81,7 +81,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom if (error.message.includes(cxschema.VERSION_MISMATCH)) { // this means the CLI version is too old. // we instruct the user to upgrade. - throw new Error(`${error.message}.\nPlease upgrade your CLI in order to interact with this app.`); + throw new Error(`This CDK CLI is not compatible with the CDK library used by your application. Please upgrade the CLI to the latest version.\n(${error.message})`); } throw error; } diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 952b9c9b2374d..99b18c3136b9f 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -173,6 +173,19 @@ export async function deployStack(options: DeployStackOptions): Promise { Changes: [], })), executeChangeSet: jest.fn((_o) => ({})), + deleteStack: jest.fn((_o) => ({})), getTemplate: jest.fn((_o) => ({ TemplateBody: JSON.stringify(DEFAULT_FAKE_TEMPLATE) })), updateTerminationProtection: jest.fn((_o) => ({ StackId: 'stack-id' })), }; @@ -190,6 +191,55 @@ test('deploy is skipped if template did not change', async () => { expect(cfnMocks.executeChangeSet).not.toBeCalled(); }); +test('if existing stack failed to create, it is deleted and recreated', async () => { + // GIVEN + givenStackExists( + { StackStatus: 'ROLLBACK_COMPLETE' }, // This is for the initial check + { StackStatus: 'DELETE_COMPLETE' }, // Poll the successful deletion + { StackStatus: 'CREATE_COMPLETE' }, // Poll the recreation + ); + givenTemplateIs({ + DifferentThan: 'TheDefault', + }); + + // WHEN + await deployStack({ + stack: FAKE_STACK, + sdk, + sdkProvider, + resolvedEnvironment: mockResolvedEnvironment(), + }); + + // THEN + expect(cfnMocks.deleteStack).toHaveBeenCalled(); + expect(cfnMocks.createChangeSet).toHaveBeenCalledWith(expect.objectContaining({ + ChangeSetType: 'CREATE', + })); +}); + +test('if existing stack failed to create, it is deleted and recreated even if the template did not change', async () => { + // GIVEN + givenStackExists( + { StackStatus: 'ROLLBACK_COMPLETE' }, // This is for the initial check + { StackStatus: 'DELETE_COMPLETE' }, // Poll the successful deletion + { StackStatus: 'CREATE_COMPLETE' }, // Poll the recreation + ); + + // WHEN + await deployStack({ + stack: FAKE_STACK, + sdk, + sdkProvider, + resolvedEnvironment: mockResolvedEnvironment(), + }); + + // THEN + expect(cfnMocks.deleteStack).toHaveBeenCalled(); + expect(cfnMocks.createChangeSet).toHaveBeenCalledWith(expect.objectContaining({ + ChangeSetType: 'CREATE', + })); +}); + test('deploy not skipped if template did not change and --force is applied', async () => { // GIVEN givenStackExists(); @@ -296,10 +346,7 @@ test('deploy not skipped if template did not change but one tag removed', async test('deploy not skipped if template changed', async () => { // GIVEN givenStackExists(); - cfnMocks.getTemplate!.mockReset(); - cfnMocks.getTemplate!.mockReturnValue({ - TemplateBody: JSON.stringify({ changed: 123 }), - }); + givenTemplateIs({ changed: 123 }); // WHEN await deployStack({ @@ -476,19 +523,37 @@ test('updateTerminationProtection called when termination protection is undefine /** * Set up the mocks so that it looks like the stack exists to start with + * + * The last element of this array will be continuously repeated. */ -function givenStackExists(overrides: Partial = {}) { +function givenStackExists(...overrides: Array>) { cfnMocks.describeStacks!.mockReset(); + + if (overrides.length === 0) { + overrides = [{}]; + } + + const baseResponse = { + StackName: 'mock-stack-name', + StackId: 'mock-stack-id', + CreationTime: new Date(), + StackStatus: 'CREATE_COMPLETE', + EnableTerminationProtection: false, + }; + + for (const override of overrides.slice(0, overrides.length - 1)) { + cfnMocks.describeStacks!.mockImplementationOnce(() => ({ + Stacks: [ {...baseResponse, ...override }], + })); + } cfnMocks.describeStacks!.mockImplementation(() => ({ - Stacks: [ - { - StackName: 'mock-stack-name', - StackId: 'mock-stack-id', - CreationTime: new Date(), - StackStatus: 'CREATE_COMPLETE', - EnableTerminationProtection: false, - ...overrides, - }, - ], + Stacks: [ {...baseResponse, ...overrides[overrides.length - 1] }], })); } + +function givenTemplateIs(template: any) { + cfnMocks.getTemplate!.mockReset(); + cfnMocks.getTemplate!.mockReturnValue({ + TemplateBody: JSON.stringify(template), + }); +} \ No newline at end of file diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index 057e50cb8e938..ec653da6d6a77 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -72,8 +72,8 @@ test('cli throws when manifest version > schema version', async () => { mockVersionNumber.restore(); } - const expectedError = `Cloud assembly schema version mismatch: Maximum schema version supported is ${currentSchemaVersion}, but found ${mockManifestVersion}.` - + '\nPlease upgrade your CLI in order to interact with this app.'; + const expectedError = 'This CDK CLI is not compatible with the CDK library used by your application. Please upgrade the CLI to the latest version.' + + `\n(Cloud assembly schema version mismatch: Maximum schema version supported is ${currentSchemaVersion}, but found ${mockManifestVersion})`; config.settings.set(['app'], 'cdk.out'); diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 21bb61dfbb9ed..6e6a4f91511c4 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -177,7 +177,6 @@ describe('CLI compatible credentials loading', () => { // WHEN const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, - ec2creds: false, profile: 'assumable', httpOptions: { proxyAddress: 'http://DOESNTMATTER/', @@ -189,6 +188,32 @@ describe('CLI compatible credentials loading', () => { // THEN -- the fake proxy agent got called, we don't care about the result expect(called).toEqual(true); }); + + test('error we get from assuming a role is useful', async () => { + // GIVEN + // Because of the way ChainableTemporaryCredentials gets its STS client, it's not mockable + // using 'mock-aws-sdk'. So instead, we have to mess around with its internals. + function makeAssumeRoleFail(s: ISDK) { + (s as any).credentials.service.assumeRole = jest.fn().mockImplementation((_request, cb) => { + cb(new Error('Nope!')); + }); + } + + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + httpOptions: { + proxyAddress: 'http://localhost:8080/', + }, + }); + + // WHEN + const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); + makeAssumeRoleFail(sdk); + + // THEN - error message contains both a helpful hint and the underlying AssumeRole message + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); + }); }); describe('Plugins', () => { diff --git a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts index a84d942c6ae52..3c674470abe4c 100644 --- a/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/bootstrapping.integtest.ts @@ -92,6 +92,27 @@ test('deploy new style synthesis to new style bootstrap', async () => { }); }); +test('deploy old style synthesis to new style bootstrap', async () => { + const bootstrapStackName = fullStackName('bootstrap-stack'); + + await cdk(['bootstrap', + '--toolkit-stack-name', bootstrapStackName, + '--qualifier', QUALIFIER, + '--cloudformation-execution-policies', 'arn:aws:iam::aws:policy/AdministratorAccess', + ], { + modEnv: { + CDK_NEW_BOOTSTRAP: '1', + }, + }); + + // Deploy stack that uses file assets + await cdkDeploy('lambda', { + options: [ + '--toolkit-stack-name', bootstrapStackName, + ], + }); +}); + test('deploying new style synthesis to old style bootstrap fails', async () => { const bootstrapStackName = fullStackName('bootstrap-stack'); diff --git a/packages/decdk/package.json b/packages/decdk/package.json index bd0e646ed35c7..46565cefab7cd 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -90,6 +90,7 @@ "@aws-cdk/aws-elasticbeanstalk": "0.0.0", "@aws-cdk/aws-elasticloadbalancing": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2-actions": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2-targets": "0.0.0", "@aws-cdk/aws-elasticsearch": "0.0.0", "@aws-cdk/aws-emr": "0.0.0", @@ -99,10 +100,12 @@ "@aws-cdk/aws-fms": "0.0.0", "@aws-cdk/aws-fsx": "0.0.0", "@aws-cdk/aws-gamelift": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", "@aws-cdk/aws-glue": "0.0.0", "@aws-cdk/aws-greengrass": "0.0.0", "@aws-cdk/aws-guardduty": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-imagebuilder": "0.0.0", "@aws-cdk/aws-inspector": "0.0.0", "@aws-cdk/aws-iot": "0.0.0", "@aws-cdk/aws-iot1click": "0.0.0", @@ -120,6 +123,7 @@ "@aws-cdk/aws-lambda-nodejs": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", "@aws-cdk/aws-logs-destinations": "0.0.0", + "@aws-cdk/aws-macie": "0.0.0", "@aws-cdk/aws-managedblockchain": "0.0.0", "@aws-cdk/aws-mediaconvert": "0.0.0", "@aws-cdk/aws-medialive": "0.0.0", @@ -177,7 +181,6 @@ "fs-extra": "^8.1.0", "jsii-reflect": "^1.5.0", "jsonschema": "^1.2.6", - "@aws-cdk/aws-elasticloadbalancingv2-actions": "0.0.0", "yaml": "1.9.2", "yargs": "^15.3.1" }, diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index cbadf0077116f..f21a25c68792a 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -194,7 +194,10 @@ "@aws-cdk/aws-detective": "0.0.0", "@aws-cdk/aws-ce": "0.0.0", "@aws-cdk/aws-synthetics": "0.0.0", - "@aws-cdk/aws-elasticloadbalancingv2-actions": "0.0.0" + "@aws-cdk/aws-elasticloadbalancingv2-actions": "0.0.0", + "@aws-cdk/aws-globalaccelerator": "0.0.0", + "@aws-cdk/aws-imagebuilder": "0.0.0", + "@aws-cdk/aws-macie": "0.0.0" }, "peerDependencies": { "constructs": "^3.0.2" diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 86188e2ef0d19..0ddee7d957395 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -39,7 +39,7 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^2.34.0", + "@typescript-eslint/eslint-plugin": "^3.0.0", "@typescript-eslint/parser": "^2.19.2", "awslint": "0.0.0", "colors": "^1.4.0", diff --git a/yarn.lock b/yarn.lock index 8b2d06e54f111..c6114d649a7c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1641,14 +1641,15 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.3.tgz#38fb31d82ed07dea87df6bd565721d11979fd761" integrity sha512-mhdQq10tYpiNncMkg1vovCud5jQm+rWeRVz6fxjCJlY6uhDlAn9GnMSmBa2DQwqPf/jS5YR0K/xChDEh1jdOQg== -"@typescript-eslint/eslint-plugin@^2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== +"@typescript-eslint/eslint-plugin@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.0.tgz#02f8ec6b5ce814bda80dfc22463f108bed1f699b" + integrity sha512-lcZ0M6jD4cqGccYOERKdMtg+VWpoq3NSnWVxpc/AwAy0zhkUYVioOUZmfNqiNH8/eBNGhCn6HXd6mKIGRgNc1Q== dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/experimental-utils" "3.0.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" + semver "^7.3.2" tsutils "^3.17.1" "@typescript-eslint/experimental-utils@2.28.0": @@ -1661,13 +1662,13 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== +"@typescript-eslint/experimental-utils@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.0.tgz#1ddf53eeb61ac8eaa9a77072722790ac4f641c03" + integrity sha512-BN0vmr9N79M9s2ctITtChRuP1+Dls0x/wlg0RXW1yQ7WJKPurg6X3Xirv61J2sjPif4F8SLsFMs5Nzte0WYoTQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" + "@typescript-eslint/typescript-estree" "3.0.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1694,10 +1695,10 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/typescript-estree@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.0.tgz#fa40e1b76ccff880130be054d9c398e96004bf42" + integrity sha512-nevQvHyNghsfLrrByzVIH4ZG3NROgJ8LZlfh3ddwPPH4CH7W4GAiSx5qu+xHuX5pWsq6q/eqMc1io840ZhAnUg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -9729,10 +9730,10 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" - integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== +uuid@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" + integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== v8-compile-cache@^2.0.3: version "2.1.0"