From 022eb66b85abba46c1a4d980259f440c31036d57 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Thu, 23 Apr 2020 10:33:57 -0700 Subject: [PATCH 01/12] fix(cli): context keys specified in `cdk.json` get moved to `cdk.context.json Removes migration logic that was introduced to move legacy context provider keys. Rationale: This logic was intended to be removed prior to v1.0 It looks for any key that contains a `:` and moves it to `cdk.context.json`. This is not expected behavior and also prevents users from having keys that have `:`. Our init templates also include feature flags which make use of the `:` character. Closes #7399 --- packages/aws-cdk/lib/settings.ts | 30 --------------------------- packages/aws-cdk/test/context.test.ts | 21 +++---------------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index dcc04d2b68f88..923c90d20afa7 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -68,8 +68,6 @@ export class Configuration { this._projectConfig = await loadAndLog(PROJECT_CONFIG); this._projectContext = await loadAndLog(PROJECT_CONTEXT); - await this.migrateLegacyContext(); - this.context = new Context( this.commandLineContext, this.projectConfig.subSettings([CONTEXT_KEY]).makeReadOnly(), @@ -99,34 +97,6 @@ export class Configuration { return this; } - - /** - * Migrate context from the 'context' field in the projectConfig object to the dedicated object - * - * Only migrate context whose key contains a ':', to migrate only context generated - * by context providers. - */ - private async migrateLegacyContext() { - const legacyContext = this.projectConfig.get([CONTEXT_KEY]); - if (legacyContext === undefined) { return; } - - const toMigrate = Object.keys(legacyContext).filter(k => k.indexOf(':') > -1); - if (toMigrate.length === 0) { return; } - - for (const key of toMigrate) { - this.projectContext.set([key], legacyContext[key]); - this.projectConfig.unset([CONTEXT_KEY, key]); - } - - // If the source object is empty now, completely remove it - if (Object.keys(this.projectConfig.get([CONTEXT_KEY])).length === 0) { - this.projectConfig.unset([CONTEXT_KEY]); - } - - // Save back - await this.projectConfig.save(PROJECT_CONFIG); - await this.projectContext.save(PROJECT_CONTEXT); - } } async function loadAndLog(fileName: string): Promise { diff --git a/packages/aws-cdk/test/context.test.ts b/packages/aws-cdk/test/context.test.ts index d60e2cab8f397..eef3117e72e63 100644 --- a/packages/aws-cdk/test/context.test.ts +++ b/packages/aws-cdk/test/context.test.ts @@ -39,21 +39,6 @@ test('load context from both files if available', async () => { expect(config.context.get('boo')).toBe('far'); }); -test('context with colons gets migrated to new file', async () => { - // GIVEN - await fs.writeJSON('cdk.context.json', { foo: 'bar' }); - await fs.writeJSON('cdk.json', { context: { 'boo': 'far', 'boo:boo': 'far:far' } }); - const config = await new Configuration().load(); - - // WHEN - config.context.set('baz', 'quux'); - await config.saveContext(); - - // THEN - expect(await fs.readJSON('cdk.context.json')).toEqual({ 'foo': 'bar', 'boo:boo': 'far:far', 'baz': 'quux' }); - expect(await fs.readJSON('cdk.json')).toEqual({ context: { boo: 'far'} }); -}); - test('deleted context disappears from new file', async () => { // GIVEN await fs.writeJSON('cdk.context.json', { foo: 'bar' }); @@ -84,7 +69,7 @@ test('clear deletes from new file', async () => { expect(await fs.readJSON('cdk.json')).toEqual({ context: { boo: 'far' } }); }); -test('surive missing new file', async () => { +test('context is preserved in the location from which it is read', async () => { // GIVEN await fs.writeJSON('cdk.json', { context: { 'boo:boo' : 'far' } }); const config = await new Configuration().load(); @@ -94,8 +79,8 @@ test('surive missing new file', async () => { await config.saveContext(); // THEN - expect(await fs.readJSON('cdk.context.json')).toEqual({ 'boo:boo' : 'far' }); - expect(await fs.readJSON('cdk.json')).toEqual({}); + expect(await fs.readJSON('cdk.context.json')).toEqual({}); + expect(await fs.readJSON('cdk.json')).toEqual({ context: { 'boo:boo' : 'far' } }); }); test('surive no context in old file', async () => { From 307c8b021d5c00c1d675f4ce3cba8004a6a4a0a8 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Thu, 23 Apr 2020 21:24:09 +0300 Subject: [PATCH 02/12] fix(eks): version update completes prematurely (#7526) The `UpdateClusterVersion` operation takes a while to begin and until then, the cluster's status is still `ACTIVE` instead `UPDATING` as expected. This causes the `isComplete` handler, which is called immediately, to think that the operation is complete, when it hasn't even began. Modify how `IsComplete` is implemented for cluster version (and config) updates. Extract the update ID and use `DescribeUpdate` to monitor the status of the update. This also allows us to fix a latent bug and fail the update in case the version update failed. The update ID is returned from `OnEvent` via a custom fields called `EksUpdateId` and passed on to the subsequent `IsComplete` invocation. This was already supported by the custom resource provider framework but not documented or officially tested, so we've added that here as well (docs + test). TESTING: Added unit tests to verify the new type of update waiter and performed a manual upgrade tests while examining the logs. Fixes #7457 --- .../lib/cluster-resource-handler/cluster.ts | 46 ++++++++++-- .../lib/cluster-resource-handler/common.ts | 18 ++++- .../lib/cluster-resource-handler/index.ts | 1 + .../@aws-cdk/aws-eks/lib/cluster-resource.ts | 1 + packages/@aws-cdk/aws-eks/lib/cluster.ts | 2 +- .../test/cluster-resource-handler-mocks.ts | 29 +++++++- .../test/integ.eks-cluster.expected.json | 39 +++++----- .../test/test.cluster-resource-provider.ts | 72 ++++++++++++++++++- .../@aws-cdk/aws-eks/test/test.cluster.ts | 2 + .../test/test.fargate-resource-provider.ts | 1 + packages/@aws-cdk/custom-resources/README.md | 9 +-- .../lib/provider-framework/types.d.ts | 5 ++ .../integ.provider.expected.json | 24 +++---- .../s3-assert-handler/index.py | 7 ++ .../test/provider-framework/runtime.test.ts | 2 + 15 files changed, 210 insertions(+), 48 deletions(-) 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 9b7859b0c2e0e..3d0c8c25e39a3 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 @@ -3,7 +3,7 @@ import { IsCompleteResponse, OnEventResponse } from '@aws-cdk/custom-resources/lib/provider-framework/types'; // eslint-disable-next-line import/no-extraneous-dependencies import * as aws from 'aws-sdk'; -import { EksClient, ResourceHandler } from './common'; +import { EksClient, ResourceEvent, ResourceHandler } from './common'; const MAX_CLUSTER_NAME_LEN = 100; @@ -19,7 +19,7 @@ export class ClusterResourceHandler extends ResourceHandler { private readonly newProps: aws.EKS.CreateClusterRequest; private readonly oldProps: Partial; - constructor(eks: EksClient, event: AWSLambda.CloudFormationCustomResourceEvent) { + constructor(eks: EksClient, event: ResourceEvent) { super(eks, event); this.newProps = parseProps(this.event.ResourceProperties); @@ -127,15 +127,17 @@ export class ClusterResourceHandler extends ResourceHandler { throw new Error(`Cannot remove cluster version configuration. Current version is ${this.oldProps.version}`); } - await this.updateClusterVersion(this.newProps.version); + return await this.updateClusterVersion(this.newProps.version); } if (updates.updateLogging || updates.updateAccess) { - await this.eks.updateClusterConfig({ + const updateResponse = await this.eks.updateClusterConfig({ name: this.clusterName, logging: this.newProps.logging, resourcesVpcConfig: this.newProps.resourcesVpcConfig, }); + + return { EksUpdateId: updateResponse.update?.id }; } // no updates @@ -144,6 +146,12 @@ export class ClusterResourceHandler extends ResourceHandler { protected async isUpdateComplete() { console.log('isUpdateComplete'); + + // if this is an EKS update, we will monitor the update event itself + if (this.event.EksUpdateId) { + return this.isEksUpdateComplete(this.event.EksUpdateId); + } + return this.isActive(); } @@ -158,7 +166,8 @@ export class ClusterResourceHandler extends ResourceHandler { return; } - await this.eks.updateClusterVersion({ name: this.clusterName, version: newVersion }); + const updateResponse = await this.eks.updateClusterVersion({ name: this.clusterName, version: newVersion }); + return { EksUpdateId: updateResponse.update?.id }; } private async isActive(): Promise { @@ -187,6 +196,33 @@ export class ClusterResourceHandler extends ResourceHandler { } } + private async isEksUpdateComplete(eksUpdateId: string) { + this.log({ isEksUpdateComplete: eksUpdateId }); + + const describeUpdateResponse = await this.eks.describeUpdate({ + name: this.clusterName, + updateId: eksUpdateId, + }); + + this.log({ describeUpdateResponse }); + + if (!describeUpdateResponse.update) { + throw new Error(`unable to describe update with id "${eksUpdateId}"`); + } + + switch (describeUpdateResponse.update.status) { + case 'InProgress': + return { IsComplete: false }; + case 'Successful': + return { IsComplete: true }; + case 'Failed': + case 'Cancelled': + throw new Error(`cluster update id "${eksUpdateId}" failed with errors: ${JSON.stringify(describeUpdateResponse.update.errors)}`); + default: + throw new Error(`unknown status "${describeUpdateResponse.update.status}" for update id "${eksUpdateId}"`); + } + } + private generateClusterName() { const suffix = this.requestId.replace(/-/g, ''); // 32 chars const prefix = this.logicalResourceId.substr(0, MAX_CLUSTER_NAME_LEN - suffix.length - 1); diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts index 1349563bf0996..57d3ae20f8cef 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/common.ts @@ -3,14 +3,25 @@ import { IsCompleteResponse, OnEventResponse } from '@aws-cdk/custom-resources/l // eslint-disable-next-line import/no-extraneous-dependencies import * as aws from 'aws-sdk'; +export interface EksUpdateId { + /** + * If this field is included in an event passed to "IsComplete", it means we + * initiated an EKS update that should be monitored using eks:DescribeUpdate + * instead of just looking at the cluster status. + */ + EksUpdateId?: string +} + +export type ResourceEvent = AWSLambda.CloudFormationCustomResourceEvent & EksUpdateId; + export abstract class ResourceHandler { protected readonly requestId: string; protected readonly logicalResourceId: string; protected readonly requestType: 'Create' | 'Update' | 'Delete'; protected readonly physicalResourceId?: string; - protected readonly event: AWSLambda.CloudFormationCustomResourceEvent; + protected readonly event: ResourceEvent; - constructor(protected readonly eks: EksClient, event: AWSLambda.CloudFormationCustomResourceEvent) { + constructor(protected readonly eks: EksClient, event: ResourceEvent) { this.requestType = event.RequestType; this.requestId = event.RequestId; this.logicalResourceId = event.LogicalResourceId; @@ -55,7 +66,7 @@ export abstract class ResourceHandler { protected abstract async onCreate(): Promise; protected abstract async onDelete(): Promise; - protected abstract async onUpdate(): Promise; + protected abstract async onUpdate(): Promise<(OnEventResponse & EksUpdateId) | void>; protected abstract async isCreateComplete(): Promise; protected abstract async isDeleteComplete(): Promise; protected abstract async isUpdateComplete(): Promise; @@ -68,6 +79,7 @@ export interface EksClient { describeCluster(request: aws.EKS.DescribeClusterRequest): Promise; updateClusterConfig(request: aws.EKS.UpdateClusterConfigRequest): Promise; updateClusterVersion(request: aws.EKS.UpdateClusterVersionRequest): Promise; + describeUpdate(req: aws.EKS.DescribeUpdateRequest): Promise; createFargateProfile(request: aws.EKS.CreateFargateProfileRequest): Promise; describeFargateProfile(request: aws.EKS.DescribeFargateProfileRequest): Promise; deleteFargateProfile(request: aws.EKS.DeleteFargateProfileRequest): Promise; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts index 7e12bc72b411f..f2b796297246a 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource-handler/index.ts @@ -16,6 +16,7 @@ const defaultEksClient: EksClient = { createCluster: req => getEksClient().createCluster(req).promise(), deleteCluster: req => getEksClient().deleteCluster(req).promise(), describeCluster: req => getEksClient().describeCluster(req).promise(), + describeUpdate: req => getEksClient().describeUpdate(req).promise(), updateClusterConfig: req => getEksClient().updateClusterConfig(req).promise(), updateClusterVersion: req => getEksClient().updateClusterVersion(req).promise(), createFargateProfile: req => getEksClient().createFargateProfile(req).promise(), diff --git a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts index e25198d5aa8d5..c449af08a407b 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster-resource.ts @@ -81,6 +81,7 @@ export class ClusterResource extends Construct { actions: [ 'eks:CreateCluster', 'eks:DescribeCluster', + 'eks:DescribeUpdate', 'eks:DeleteCluster', 'eks:UpdateClusterVersion', 'eks:UpdateClusterConfig', diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 11013d34fc9a0..d035e341f1fde 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1078,6 +1078,6 @@ export enum MachineImageType { const GPU_INSTANCETYPES = ['p2', 'p3', 'g4']; -export function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { +function nodeTypeForInstanceType(instanceType: ec2.InstanceType) { return GPU_INSTANCETYPES.includes(instanceType.toString().substring(0, 2)) ? NodeType.GPU : NodeType.STANDARD; } diff --git a/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts b/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts index 7bcc866024418..c7980e0a89cf4 100644 --- a/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts +++ b/packages/@aws-cdk/aws-eks/test/cluster-resource-handler-mocks.ts @@ -9,6 +9,7 @@ export let actualRequest: { configureAssumeRoleRequest?: sdk.STS.AssumeRoleRequest; createClusterRequest?: sdk.EKS.CreateClusterRequest; describeClusterRequest?: sdk.EKS.DescribeClusterRequest; + describeUpdateRequest?: sdk.EKS.DescribeUpdateRequest; deleteClusterRequest?: sdk.EKS.DeleteClusterRequest; updateClusterConfigRequest?: sdk.EKS.UpdateClusterConfigRequest; updateClusterVersionRequest?: sdk.EKS.UpdateClusterVersionRequest; @@ -22,6 +23,8 @@ export let actualRequest: { */ export let simulateResponse: { describeClusterResponseMockStatus?: string; + describeUpdateResponseMockStatus?: string; + describeUpdateResponseMockErrors?: sdk.EKS.ErrorDetails; deleteClusterErrorCode?: string; describeClusterExceptionCode?: string; } = { }; @@ -31,6 +34,8 @@ export function reset() { simulateResponse = { }; } +export const MOCK_UPDATE_STATUS_ID = 'MockEksUpdateStatusId'; + export const client: EksClient = { configureAssumeRole: req => { @@ -87,14 +92,34 @@ export const client: EksClient = { }; }, + describeUpdate: async req => { + actualRequest.describeUpdateRequest = req; + + return { + update: { + id: req.updateId, + errors: simulateResponse.describeUpdateResponseMockErrors, + status: simulateResponse.describeUpdateResponseMockStatus, + }, + }; + }, + updateClusterConfig: async req => { actualRequest.updateClusterConfigRequest = req; - return { }; + return { + update: { + id: MOCK_UPDATE_STATUS_ID, + }, + }; }, updateClusterVersion: async req => { actualRequest.updateClusterVersionRequest = req; - return { }; + return { + update: { + id: MOCK_UPDATE_STATUS_ID, + }, + }; }, createFargateProfile: async req => { 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 1f88a2c6b34b0..c168d90725028 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 @@ -802,6 +802,7 @@ "Action": [ "eks:CreateCluster", "eks:DescribeCluster", + "eks:DescribeUpdate", "eks:DeleteCluster", "eks:UpdateClusterVersion", "eks:UpdateClusterConfig", @@ -2231,7 +2232,7 @@ }, "/", { - "Ref": "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69S3Bucket835D19A2" + "Ref": "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103S3Bucket4281E0A4" }, "/", { @@ -2241,7 +2242,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69S3VersionKeyBFF2DA61" + "Ref": "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103S3VersionKey3B54BD32" } ] } @@ -2254,7 +2255,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69S3VersionKeyBFF2DA61" + "Ref": "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103S3VersionKey3B54BD32" } ] } @@ -2264,11 +2265,11 @@ ] }, "Parameters": { - "referencetoawscdkeksclustertestAssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3Bucket57C4C68FRef": { - "Ref": "AssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3Bucket7CCCFC30" + "referencetoawscdkeksclustertestAssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3Bucket38D74D5ERef": { + "Ref": "AssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3BucketD006BE3B" }, - "referencetoawscdkeksclustertestAssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3VersionKeyBB973CE7Ref": { - "Ref": "AssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3VersionKeyA2A28538" + "referencetoawscdkeksclustertestAssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3VersionKey189EBCBARef": { + "Ref": "AssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3VersionKeyEC71339F" }, "referencetoawscdkeksclustertestAssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3BucketC7CBF350Ref": { "Ref": "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C" @@ -2413,17 +2414,17 @@ } }, "Parameters": { - "AssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3Bucket7CCCFC30": { + "AssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3BucketD006BE3B": { "Type": "String", - "Description": "S3 bucket for asset \"54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8\"" + "Description": "S3 bucket for asset \"c0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602\"" }, - "AssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8S3VersionKeyA2A28538": { + "AssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602S3VersionKeyEC71339F": { "Type": "String", - "Description": "S3 key for asset version \"54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8\"" + "Description": "S3 key for asset version \"c0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602\"" }, - "AssetParameters54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8ArtifactHashC0D1FA2A": { + "AssetParametersc0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602ArtifactHash35F5D0CC": { "Type": "String", - "Description": "Artifact hash for asset \"54c9eae68c19c65a224969094e8447eed31d811384c7e32bdb72fffb4be15ac8\"" + "Description": "Artifact hash for asset \"c0e453b77d5ccf090915fba7c771380f8370da5cbcc3c7ed757c98addd75b602\"" }, "AssetParameters5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1S3Bucket663A709C": { "Type": "String", @@ -2449,17 +2450,17 @@ "Type": "String", "Description": "Artifact hash for asset \"a6d508eaaa0d3cddbb47a84123fc878809c8431c5466f360912f70b5b9770afb\"" }, - "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69S3Bucket835D19A2": { + "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103S3Bucket4281E0A4": { "Type": "String", - "Description": "S3 bucket for asset \"5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69\"" + "Description": "S3 bucket for asset \"fa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103\"" }, - "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69S3VersionKeyBFF2DA61": { + "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103S3VersionKey3B54BD32": { "Type": "String", - "Description": "S3 key for asset version \"5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69\"" + "Description": "S3 key for asset version \"fa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103\"" }, - "AssetParameters5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69ArtifactHash0E7708FD": { + "AssetParametersfa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103ArtifactHash733CC5DF": { "Type": "String", - "Description": "Artifact hash for asset \"5c7de45abd07f88cf62deefa6399553786f3559084ff34bae66042cdd1987d69\"" + "Description": "Artifact hash for asset \"fa73027e9f72f21daca2d67aa5a23e88f87d90536a7e3c36de9adbfb27fa9103\"" }, "AssetParameters36525a61abfaf5764fad460fd03c24215fd00da60805807d6138c51be4d03dbcS3Bucket2D824DEF": { "Type": "String", 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 33a7431894572..0759704acead4 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 @@ -353,7 +353,74 @@ export = { }, }, + 'isUpdateComplete with EKS update ID': { + + async 'with "Failed" status'(test: Test) { + const event = mocks.newRequest('Update'); + const isCompleteHandler = new ClusterResourceHandler(mocks.client, { + ...event, + EksUpdateId: 'foobar', + }); + + mocks.simulateResponse.describeUpdateResponseMockStatus = 'Failed'; + mocks.simulateResponse.describeUpdateResponseMockErrors = [ + { + errorMessage: 'errorMessageMock', + errorCode: 'errorCodeMock', + resourceIds: [ + 'foo', 'bar', + ], + }, + ]; + + let error; + try { + await isCompleteHandler.isComplete(); + } catch (e) { + error = e; + } + test.ok(error); + test.deepEqual(mocks.actualRequest.describeUpdateRequest, { name: 'physical-resource-id', updateId: 'foobar' }); + test.equal(error.message, 'cluster update id "foobar" failed with errors: [{"errorMessage":"errorMessageMock","errorCode":"errorCodeMock","resourceIds":["foo","bar"]}]'); + test.done(); + }, + + async 'with "InProgress" status, returns IsComplete=false'(test: Test) { + const event = mocks.newRequest('Update'); + const isCompleteHandler = new ClusterResourceHandler(mocks.client, { + ...event, + EksUpdateId: 'foobar', + }); + + mocks.simulateResponse.describeUpdateResponseMockStatus = 'InProgress'; + + const response = await isCompleteHandler.isComplete(); + + test.deepEqual(mocks.actualRequest.describeUpdateRequest, { name: 'physical-resource-id', updateId: 'foobar' }); + test.equal(response.IsComplete, false); + test.done(); + }, + + async 'with "Successful" status, returns IsComplete=true'(test: Test) { + const event = mocks.newRequest('Update'); + const isCompleteHandler = new ClusterResourceHandler(mocks.client, { + ...event, + EksUpdateId: 'foobar', + }); + + mocks.simulateResponse.describeUpdateResponseMockStatus = 'Successful'; + + const response = await isCompleteHandler.isComplete(); + + test.deepEqual(mocks.actualRequest.describeUpdateRequest, { name: 'physical-resource-id', updateId: 'foobar' }); + test.equal(response.IsComplete, true); + test.done(); + }, + + }, + 'in-place': { + 'version change': { async 'from undefined to a specific value'(test: Test) { const handler = new ClusterResourceHandler(mocks.client, mocks.newRequest('Update', { @@ -362,7 +429,7 @@ export = { version: undefined, })); const resp = await handler.onEvent(); - test.equal(resp, undefined); + test.deepEqual(resp, { EksUpdateId: mocks.MOCK_UPDATE_STATUS_ID }); test.deepEqual(mocks.actualRequest.updateClusterVersionRequest!, { name: 'physical-resource-id', version: '12.34', @@ -377,8 +444,9 @@ export = { }, { version: '1.1', })); + const resp = await handler.onEvent(); - test.equal(resp, undefined); + test.deepEqual(resp, { EksUpdateId: mocks.MOCK_UPDATE_STATUS_ID }); test.deepEqual(mocks.actualRequest.updateClusterVersionRequest!, { name: 'physical-resource-id', version: '2.0', diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index eb2007c70e627..7e67d9f0c8632 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -831,6 +831,7 @@ export = { Action: [ 'eks:CreateCluster', 'eks:DescribeCluster', + 'eks:DescribeUpdate', 'eks:DeleteCluster', 'eks:UpdateClusterVersion', 'eks:UpdateClusterConfig', @@ -941,6 +942,7 @@ export = { Action: [ 'eks:CreateCluster', 'eks:DescribeCluster', + 'eks:DescribeUpdate', 'eks:DeleteCluster', 'eks:UpdateClusterVersion', 'eks:UpdateClusterConfig', diff --git a/packages/@aws-cdk/aws-eks/test/test.fargate-resource-provider.ts b/packages/@aws-cdk/aws-eks/test/test.fargate-resource-provider.ts index 873b1b499cdbe..5c119ae7a9c4e 100644 --- a/packages/@aws-cdk/aws-eks/test/test.fargate-resource-provider.ts +++ b/packages/@aws-cdk/aws-eks/test/test.fargate-resource-provider.ts @@ -287,6 +287,7 @@ function newEksClientMock() { createCluster: sinon.fake.throws('not implemented'), deleteCluster: sinon.fake.throws('not implemented'), describeCluster: sinon.fake.throws('not implemented'), + describeUpdate: sinon.fake.throws('not implemented'), updateClusterConfig: sinon.fake.throws('not implemented'), updateClusterVersion: sinon.fake.throws('not implemented'), configureAssumeRole: sinon.fake(), diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index d30028903575d..7d73424d9d12e 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -137,6 +137,7 @@ The return value from `onEvent` must be a JSON object with the following fields: |-----|----|--------|----------- |`PhysicalResourceId`|String|No|The allocated/assigned physical ID of the resource. If omitted for `Create` events, the event's `RequestId` will be used. For `Update`, the current physical ID will be used. If a different value is returned, CloudFormation will follow with a subsequent `Delete` for the previous ID (resource replacement). For `Delete`, it will always return the current physical resource ID, and if the user returns a different one, an error will occur. |`Data`|JSON|No|Resource attributes, which can later be retrieved through `Fn::GetAtt` on the custom resource object. +|*any*|*any*|No|Any other field included in the response will be passed through to `isComplete`. This can sometimes be useful to pass state between the handlers. [Custom Resource Provider Request]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html#crpg-ref-request-fields @@ -158,10 +159,10 @@ with the message "Operation timed out". If an error is thrown, the framework will submit a "FAILED" response to AWS CloudFormation. -The input event to `isComplete` is similar to -[`onEvent`](#handling-lifecycle-events-onevent), with an additional guarantee -that `PhysicalResourceId` is defines and contains the value returned from -`onEvent` or the described default. At any case, it is guaranteed to exist. +The input event to `isComplete` includes all request fields, combined with all +fields returned from `onEvent`. If `PhysicalResourceId` has not been explicitly +returned from `onEvent`, it's value will be calculated based on the heuristics +described above. The return value must be a JSON object with the following fields: diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/types.d.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/types.d.ts index 058405bf4e928..33a125a971cca 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/types.d.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/types.d.ts @@ -75,6 +75,11 @@ interface OnEventResponse { * Resource attributes to return. */ readonly Data?: { [name: string]: any }; + + /** + * Custom fields returned from OnEvent will be passed to IsComplete. + */ + readonly [key: string]: any; } /** diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json index a2307af6ac1ab..9907ab690dd70 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integ.provider.expected.json @@ -340,7 +340,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3Bucket776FD46E" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF" }, "S3Key": { "Fn::Join": [ @@ -353,7 +353,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3VersionKeyA70347F9" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3VersionKey67FE4034" } ] } @@ -366,7 +366,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3VersionKeyA70347F9" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3VersionKey67FE4034" } ] } @@ -450,7 +450,7 @@ "Properties": { "Code": { "S3Bucket": { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3Bucket776FD46E" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF" }, "S3Key": { "Fn::Join": [ @@ -463,7 +463,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3VersionKeyA70347F9" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3VersionKey67FE4034" } ] } @@ -476,7 +476,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3VersionKeyA70347F9" + "Ref": "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3VersionKey67FE4034" } ] } @@ -1054,17 +1054,17 @@ "Type": "String", "Description": "Artifact hash for asset \"5e49cf64d8027f48872790f80cdb76c5b836ecf9a70b71be1eb937a5c25a47c1\"" }, - "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3Bucket776FD46E": { + "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3Bucket0DB889DF": { "Type": "String", - "Description": "S3 bucket for asset \"db961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511\"" + "Description": "S3 bucket for asset \"4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8\"" }, - "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511S3VersionKeyA70347F9": { + "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8S3VersionKey67FE4034": { "Type": "String", - "Description": "S3 key for asset version \"db961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511\"" + "Description": "S3 key for asset version \"4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8\"" }, - "AssetParametersdb961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511ArtifactHashB3EA6E4A": { + "AssetParameters4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8ArtifactHash6C17CFC2": { "Type": "String", - "Description": "Artifact hash for asset \"db961fc9d087616ad76339bd5135f518cea24001f866a17067a1024235128511\"" + "Description": "Artifact hash for asset \"4bafad8d010ba693e235b77d2c6decfc2ac79a8208d4477cbb36d31caf7189e8\"" } }, "Outputs": { diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py index a5e9321c895ea..0f99bdca49aa9 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/integration-test-fixtures/s3-assert-handler/index.py @@ -4,10 +4,17 @@ def on_event(event, ctx): print(event) + return { + 'ArbitraryField': 12345 + } def is_complete(event, ctx): print(event) + # verify result from on_event is passed through + if event.get('ArbitraryField', None) != 12345: + raise 'Error: expecting "event" to include "ArbitraryField" with value 12345' + # nothing to assert if this resource is being deleted if event['RequestType'] == 'Delete': return { 'IsComplete': True } diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts index 0f1679955ed06..3898246fab6c4 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts @@ -32,11 +32,13 @@ test('async flow: isComplete returns true only after 3 times', async () => { return { PhysicalResourceId: MOCK_PHYSICAL_ID, Data: MOCK_ATTRS, + ArbitraryField: 1234, }; }; mocks.isCompleteImplMock = async event => { isCompleteCalls++; + expect((event as any).ArbitraryField).toEqual(1234); // any field is passed through expect(event.PhysicalResourceId).toEqual(MOCK_PHYSICAL_ID); // physical ID returned from onEvent is passed to "isComplete" expect(event.Data).toStrictEqual(MOCK_ATTRS); // attributes are propagated between the calls From 976af0d92402408a78d57c7bc3aa8de6ec3f4252 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2020 19:27:02 +0000 Subject: [PATCH 03/12] chore(deps): bump aws-sdk from 2.661.0 to 2.662.0 (#7570) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.661.0 to 2.662.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.661.0...v2.662.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/@aws-cdk/aws-cloudfront/package.json | 2 +- packages/@aws-cdk/aws-cloudtrail/package.json | 2 +- packages/@aws-cdk/aws-codebuild/package.json | 2 +- packages/@aws-cdk/aws-codecommit/package.json | 2 +- packages/@aws-cdk/aws-dynamodb/package.json | 2 +- packages/@aws-cdk/aws-eks/package.json | 2 +- .../@aws-cdk/aws-events-targets/package.json | 2 +- packages/@aws-cdk/aws-lambda/package.json | 2 +- packages/@aws-cdk/aws-route53/package.json | 2 +- packages/@aws-cdk/aws-sqs/package.json | 2 +- .../@aws-cdk/custom-resources/package.json | 2 +- packages/aws-cdk/package.json | 2 +- packages/cdk-assets/package.json | 2 +- yarn.lock | 126 ++---------------- 14 files changed, 24 insertions(+), 128 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 336e2ec225cb3..b615c3d752311 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index e345112c26b74..57a4717ed420d 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 2ef2da86c3b87..ff417756adc4d 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -70,7 +70,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 8cf5db589ce19..0145879053ede 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -70,7 +70,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index fb1549b720a0a..c94be2dad2c54 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/jest": "^25.2.1", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index bda1512c0b441..33143631c1ece 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 676a8cc6c378d..e49e9e4801733 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -86,7 +86,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 4f5ed050a7e2d..0e470f76207a5 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,7 +71,7 @@ "@types/lodash": "^4.14.150", "@types/nodeunit": "^0.0.30", "@types/sinon": "^9.0.0", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index c1ef16decf786..ec0e01f5dfa65 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index b67ccc49ad438..1a1674b0ce4da 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.30", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 76ad934b6062f..7f93803384392 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.0", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 2fb4a7ad5e583..560ddbe642437 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -68,7 +68,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index d701abf862e6a..b4dca1c7f7f1c 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -44,7 +44,7 @@ "dependencies": { "@aws-cdk/cdk-assets-schema": "0.0.0", "archiver": "^4.0.1", - "aws-sdk": "^2.661.0", + "aws-sdk": "^2.662.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/yarn.lock b/yarn.lock index d636c4b8052a9..a6d468fe2d55b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2636,11 +2636,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2891,7 +2886,7 @@ available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: +aws-sdk-mock@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== @@ -2900,25 +2895,10 @@ aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.596.0: - version "2.660.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.660.0.tgz#1be2f814ffdb1aadf859b252601974073a39a4b2" - integrity sha512-6FR91Jg1x9TuFglsdBHkRuE4X7sPRwqeTB2GwLk9XPX1giicdMvJrWbcw5rUnMKjXs9LVlkwaK5VI9AJ0d8dpw== - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.637.0, aws-sdk@^2.661.0: - version "2.661.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.661.0.tgz#e877dbc0d07b74e93e2383eb4cd0407592b1e46e" - integrity sha512-dfGtbRQQUmcpj6WGVhj7q2PADCvDhLf+/aRGPXcMrm0cnHavkmHPVaSvrw2lJJJ5N9MsBKoUyacrVcIQkfNsgw== +aws-sdk@^2.637.0, aws-sdk@^2.662.0: + version "2.662.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.662.0.tgz#c41c809648104b8aba6276e12eb7ad43a6fca40d" + integrity sha512-emZOIKHCV2EHByRNAm7d++1ugzRpLx4WhMm+T9ydy/OyKcOGVAAMPVW0Mib5GXuWpC6A6aE7ACi3pZTjK9LMQA== dependencies: buffer "4.9.1" events "1.1.1" @@ -4892,21 +4872,11 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-json@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" - integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== - dotenv@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== -dotenv@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== - dotgitignore@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -5141,11 +5111,6 @@ escodegen@~1.9.0: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^14.1.0: - version "14.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" - integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== - eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" @@ -5173,15 +5138,7 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== - dependencies: - eslint-utils "^1.4.2" - regexpp "^3.0.0" - -eslint-plugin-import@^2.19.1, eslint-plugin-import@^2.20.2: +eslint-plugin-import@^2.20.2: version "2.20.2" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== @@ -5199,28 +5156,6 @@ eslint-plugin-import@^2.19.1, eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== - dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-promise@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" - integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== - -eslint-plugin-standard@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" - integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== - eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -5229,7 +5164,7 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2, eslint-utils@^1.4.3: +eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== @@ -6443,11 +6378,6 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1: - version "5.1.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" - integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7834,7 +7764,7 @@ jest@^24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" -jest@^25.3.0, jest@^25.4.0: +jest@^25.4.0: version "25.4.0" resolved "https://registry.yarnpkg.com/jest/-/jest-25.4.0.tgz#fb96892c5c4e4a6b9bcb12068849cddf4c5f8cc7" integrity sha512-XWipOheGB4wai5JfCYXd6vwsWNwM/dirjRoZgAa7H2wd8ODWbli2AiKjqG8AYhyx+8+5FBEdpO92VhGlBydzbw== @@ -8188,24 +8118,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lambda-leak@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" - integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= - -lambda-tester@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" - integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== - dependencies: - app-root-path "^2.2.1" - dotenv "^8.0.0" - dotenv-json "^1.0.0" - lambda-leak "^2.0.0" - semver "^6.1.1" - uuid "^3.3.2" - vandium-utils "^1.1.1" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -8996,17 +8908,6 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^11.7.0: - version "11.9.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-11.9.1.tgz#2b026c5beb6d0dbcb41e7e4cefa671bc36db9c61" - integrity sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA== - dependencies: - debug "^4.1.0" - json-stringify-safe "^5.0.1" - lodash "^4.17.13" - mkdirp "^0.5.0" - propagate "^2.0.0" - nock@^12.0.3: version "12.0.3" resolved "https://registry.yarnpkg.com/nock/-/nock-12.0.3.tgz#83f25076dbc4c9aa82b5cdf54c9604c7a778d1c9" @@ -11093,7 +10994,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0: +resolve@1.x, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0: version "1.16.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.1.tgz#49fac5d8bacf1fd53f200fa51247ae736175832c" integrity sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig== @@ -11262,7 +11163,7 @@ semver-intersect@^1.4.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -12433,7 +12334,7 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= -ts-jest@^25.3.1, ts-jest@^25.4.0: +ts-jest@^25.4.0: version "25.4.0" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.4.0.tgz#5ad504299f8541d463a52e93e5e9d76876be0ba4" integrity sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw== @@ -12866,11 +12767,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vandium-utils@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" - integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= - vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" From a1d98845a209e7ed650d8adaaa1a724a3109b6a2 Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Thu, 23 Apr 2020 13:10:15 -0700 Subject: [PATCH 04/12] fix(stepfunctions-tasks): cannot specify part of execution data or task context as input to the `RunLambda` service integration (#7428) The Lambda service integration requires an input field called `payload`. We modeled this as a `{[key: string]: any}` which precludes the usage of execution data or context data as inputs to a Lambda function. Fix: Change the type of `payload` to be `TaskInput` which has already modeled a type union for task classes that accept multiple types of payload. This will enable usage of literal strings, objects, execution data, and task context as input types supported for invoking a Lambda function. Rationale: Although this is a breaking change, the workarounds for enabling usage of different types is not user friendly and incomplete as all of the types above cannot be expressed in the current modeling of `payload` Fixes #7371 BREAKING CHANGE: `payload` in RunLambdaTask is now of type `TaskInput` and has a default of the state input instead of the empty object. You can migrate your current assignment to payload by supplying it to the `TaskInput.fromObject()` API --- .../lib/lambda/run-lambda-task.ts | 6 +- .../test/lambda/integ.invoke-function.ts | 4 +- .../lambda/integ.run-lambda.expected.json | 207 ++++++++++++++++++ .../test/lambda/integ.run-lambda.ts | 68 ++++++ .../test/lambda/run-lambda-task.test.ts | 74 ++++++- packages/@aws-cdk/aws-stepfunctions/README.md | 67 +++++- 6 files changed, 415 insertions(+), 11 deletions(-) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts index 4f3c66c92178b..891f3596ecd62 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/lambda/run-lambda-task.ts @@ -10,9 +10,9 @@ export interface RunLambdaTaskProps { /** * The JSON that you want to provide to your Lambda function as input. * - * @default - No payload + * @default - The state input (JSON path '$') */ - readonly payload?: { [key: string]: any }; + readonly payload?: sfn.TaskInput; /** * The service integration pattern indicates different ways to invoke Lambda function. @@ -92,7 +92,7 @@ export class RunLambdaTask implements sfn.IStepFunctionsTask { metricDimensions: { LambdaFunctionArn: this.lambdaFunction.functionArn }, parameters: { FunctionName: this.lambdaFunction.functionName, - Payload: this.props.payload, + Payload: this.props.payload ? this.props.payload.value : sfn.TaskInput.fromDataAt('$').value, InvocationType: this.props.invocationType, ClientContext: this.props.clientContext, Qualifier: this.props.qualifier, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts index 4b6ab021ac64d..d3791f59782ed 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.invoke-function.ts @@ -26,9 +26,9 @@ const callbackHandler = new Function(stack, 'CallbackHandler', { const taskTokenHandler = new sfn.Task(stack, 'Invoke Handler with task token', { task: new tasks.RunLambdaTask(callbackHandler, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - payload: { + payload: sfn.TaskInput.fromObject({ token: sfn.Context.taskToken, - }, + }), }), inputPath: '$.guid', resultPath: '$.status', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json new file mode 100644 index 0000000000000..64f8d2444d7f2 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.expected.json @@ -0,0 +1,207 @@ +{ + "Resources": { + "submitJobLambdaServiceRole4D897ABD": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "submitJobLambdaEFB00F3C": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async () => {\n return {\n statusCode: '200',\n body: 'hello, world!'\n };\n };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "submitJobLambdaServiceRole4D897ABD", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "submitJobLambdaServiceRole4D897ABD" + ] + }, + "checkJobStateLambdaServiceRoleB8B57B65": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "checkJobStateLambda4618B7B7": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = async function(event, context) {\n return {\n status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED'\n };\n };" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "checkJobStateLambdaServiceRoleB8B57B65", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "checkJobStateLambdaServiceRoleB8B57B65" + ] + }, + "StateMachineRoleB840431D": { + "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" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "submitJobLambdaEFB00F3C", + "Arn" + ] + } + }, + { + "Action": "lambda:InvokeFunction", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "checkJobStateLambda4618B7B7", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Invoke Handler\",\"States\":{\"Invoke Handler\":{\"Next\":\"Check the job state\",\"Parameters\":{\"FunctionName\":\"", + { + "Ref": "submitJobLambdaEFB00F3C" + }, + "\",\"Payload.$\":\"$\"},\"OutputPath\":\"$.Payload\",\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::lambda:invoke\"},\"Check the job state\":{\"Next\":\"Job Complete?\",\"Parameters\":{\"FunctionName\":\"", + { + "Ref": "checkJobStateLambda4618B7B7" + }, + "\",\"Payload.$\":\"$\"},\"OutputPath\":\"$.Payload\",\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::lambda:invoke\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Final step\"}]},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"Received a status that was not 200\",\"Cause\":\"Job Failed\"},\"Final step\":{\"Type\":\"Pass\",\"End\":true}},\"TimeoutSeconds\":30}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + } + }, + "DependsOn": [ + "StateMachineRoleDefaultPolicyDF1E6607", + "StateMachineRoleB840431D" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.ts new file mode 100644 index 0000000000000..dcce6c0ed5d08 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/integ.run-lambda.ts @@ -0,0 +1,68 @@ +import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; +import * as sfn from '@aws-cdk/aws-stepfunctions'; +import * as cdk from '@aws-cdk/core'; +import * as tasks from '../../lib'; + +/* + * Stack verification steps: + * The generated State Machine can be executed from the CLI (or Step Functions console) + * and runs with an execution status of `Succeeded`. + * + * -- aws stepfunctions start-execution --state-machine-arn provides execution arn + * -- aws stepfunctions describe-execution --execution-arn returns a status of `Succeeded` + */ +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-stepfunctions-tasks-run-lambda-integ'); + +const submitJobLambda = new Function(stack, 'submitJobLambda', { + code: Code.fromInline(`exports.handler = async () => { + return { + statusCode: '200', + body: 'hello, world!' + }; + };`), + runtime: Runtime.NODEJS_10_X, + handler: 'index.handler', +}); + +const submitJob = new sfn.Task(stack, 'Invoke Handler', { + task: new tasks.RunLambdaTask(submitJobLambda), + outputPath: '$.Payload', +}); + +const checkJobStateLambda = new Function(stack, 'checkJobStateLambda', { + code: Code.fromInline(`exports.handler = async function(event, context) { + return { + status: event.statusCode === '200' ? 'SUCCEEDED' : 'FAILED' + }; + };`), + runtime: Runtime.NODEJS_10_X, + handler: 'index.handler', +}); + +const checkJobState = new sfn.Task(stack, 'Check the job state', { + task: new tasks.RunLambdaTask(checkJobStateLambda), + outputPath: '$.Payload', +}); + +const isComplete = new sfn.Choice(stack, 'Job Complete?'); +const jobFailed = new sfn.Fail(stack, 'Job Failed', { + cause: 'Job Failed', + error: 'Received a status that was not 200', +}); +const finalStatus = new sfn.Pass(stack, 'Final step'); + +const chain = sfn.Chain.start(submitJob) + .next(checkJobState) + .next( + isComplete + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus), + ); + +new sfn.StateMachine(stack, 'StateMachine', { + definition: chain, + timeout: cdk.Duration.seconds(30), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts index 3aee22f9124ee..6d46eeaf368cb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/lambda/run-lambda-task.test.ts @@ -18,9 +18,9 @@ beforeEach(() => { test('Invoke lambda with default magic ARN', () => { const task = new sfn.Task(stack, 'Task', { task: new tasks.RunLambdaTask(fn, { - payload: { + payload: sfn.TaskInput.fromObject({ foo: 'bar', - }, + }), invocationType: tasks.InvocationType.REQUEST_RESPONSE, clientContext: 'eyJoZWxsbyI6IndvcmxkIn0=', qualifier: '1', @@ -63,9 +63,9 @@ test('Lambda function can be used in a Task with Task Token', () => { const task = new sfn.Task(stack, 'Task', { task: new tasks.RunLambdaTask(fn, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, - payload: { + payload: sfn.TaskInput.fromObject({ token: sfn.Context.taskToken, - }, + }), }), }); new sfn.StateMachine(stack, 'SM', { @@ -98,6 +98,72 @@ test('Lambda function can be used in a Task with Task Token', () => { }); }); +test('Lambda function is invoked with the state input as payload by default', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], + ], + }, + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', + }, + }); +}); + +test('Lambda function can be provided with the state input as the payload', () => { + const task = new sfn.Task(stack, 'Task', { + task: new tasks.RunLambdaTask(fn, { + payload: sfn.TaskInput.fromDataAt('$'), + }), + }); + new sfn.StateMachine(stack, 'SM', { + definition: task, + }); + + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::lambda:invoke', + ], + ], + }, + End: true, + Parameters: { + 'FunctionName': { + Ref: 'Fn9270CBC0', + }, + 'Payload.$': '$', + }, + }); +}); + test('Task throws if WAIT_FOR_TASK_TOKEN is supplied but task token is not included in payLoad', () => { expect(() => { new sfn.Task(stack, 'Task', { diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 534ce7e1f3aa1..78233bd085c17 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -159,17 +159,80 @@ similar to (for example) `inputPath`. #### Lambda example +[Invoke](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html) a Lambda function. + +You can specify the input to your Lambda function through the `payload` attribute. +By default, Step Functions invokes Lambda function with the state input (JSON path '$') +as the input. + +The following snippet invokes a Lambda Function with the state input as the payload +by referencing the `$` path. + +```ts +new sfn.Task(this, 'Invoke with state input'); +``` + +When a function is invoked, the Lambda service sends [these response +elements](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_ResponseElements) +back. + +⚠️ The response from the Lambda function is in an attribute called `Payload` + +The following snippet invokes a Lambda Function by referencing the `$.Payload` path +to reference the output of a Lambda executed before it. + ```ts - const task = new sfn.Task(stack, 'Invoke2', { +new sfn.Task(this, 'Invoke with empty object as payload', { + task: new tasks.RunLambdaTask(myLambda, { + payload: sfn.TaskInput.fromObject({}) + }), +}); + +new sfn.Task(this, 'Invoke with payload field in the state input', { + task: new tasks.RunLambdaTask(myOtherLambda, { + payload: sfn.TaskInput.fromDataAt('$.Payload'), + }), +}); +``` + +The following snippet invokes a Lambda and sets the task output to only include +the Lambda function response. + +```ts +new sfn.Task(this, 'Invoke and set function response as task output', { + task: new tasks.RunLambdaTask(myLambda, { + payload: sfn.TaskInput.fromDataAt('$'), + }), + outputPath: '$.Payload', +}); +``` + +You can have Step Functions pause a task, and wait for an external process to +return a task token. Read more about the [callback pattern](https://docs.aws.amazon.com/step-functions/latest/dg/callback-task-sample-sqs.html#call-back-lambda-example) + +To use the callback pattern, set the `token` property on the task. Call the Step +Functions `SendTaskSuccess` or `SendTaskFailure` APIs with the token to +indicate that the task has completed and the state machine should resume execution. + +The following snippet invokes a Lambda with the task token as part of the input +to the Lambda. + +```ts + const task = new sfn.Task(stack, 'Invoke with callback', { task: new tasks.RunLambdaTask(myLambda, { integrationPattern: sfn.ServiceIntegrationPattern.WAIT_FOR_TASK_TOKEN, payload: { - token: sfn.Context.taskToken + token: sfn.Context.taskToken, + input: sfn.TaskInput.fromDataAt('$.someField'), } }) }); ``` +⚠️ The task will pause until it receives that task token back with a `SendTaskSuccess` or `SendTaskFailure` +call. Learn more about [Callback with the Task +Token](https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token). + #### Glue Job example ```ts From eff1bf3e6cdbf07cfa9b1c532ddfe00db3b6ee68 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2020 22:11:32 +0000 Subject: [PATCH 05/12] chore(deps-dev): bump @types/yaml from 1.2.0 to 1.9.7 (#7582) Bumps [@types/yaml](https://github.com/eemeli/yaml) from 1.2.0 to 1.9.7. - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/commits) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/aws-cdk/package.json | 2 +- packages/cdk-dasm/package.json | 2 +- packages/decdk/package.json | 2 +- yarn.lock | 12 +++++++----- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 560ddbe642437..6ea62b353060e 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -50,7 +50,7 @@ "@types/sinon": "^9.0.0", "@types/table": "^4.0.7", "@types/uuid": "^7.0.2", - "@types/yaml": "^1.2.0", + "@types/yaml": "^1.9.7", "@types/yargs": "^15.0.4", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index aa6edd21d75bf..d35b7f3f69ba5 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/jest": "^25.2.1", - "@types/yaml": "1.2.0", + "@types/yaml": "1.9.7", "jest": "^25.4.0" }, "keywords": [ diff --git a/packages/decdk/package.json b/packages/decdk/package.json index a4c7de508fdfe..c117d39aefecc 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -181,7 +181,7 @@ "devDependencies": { "@types/fs-extra": "^8.1.0", "@types/jest": "^25.2.1", - "@types/yaml": "1.2.0", + "@types/yaml": "1.9.7", "@types/yargs": "^15.0.4", "jest": "^25.4.0", "jsii": "^1.4.1" diff --git a/yarn.lock b/yarn.lock index a6d468fe2d55b..12f06a9eec5a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2347,10 +2347,12 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.2.tgz#d680a9c596ef84abf5c4c07a32ffd66d582526f8" integrity sha512-8Ly3zIPTnT0/8RCU6Kg/G3uTICf9sRwYOpUzSIM3503tLIKcnJPRuinHhXngJUy2MntrEf6dlpOHXJju90Qh5w== -"@types/yaml@1.2.0", "@types/yaml@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.2.0.tgz#4ed577fc4ebbd6b829b28734e56d10c9e6984e09" - integrity sha512-GW8b9qM+ebgW3/zjzPm0I1NxMvLaz/YKT9Ph6tTb+Fkeyzd9yLTvQ6ciQ2MorTRmb/qXmfjMerRpG4LviixaqQ== +"@types/yaml@1.9.7", "@types/yaml@^1.9.7": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" + integrity sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA== + dependencies: + yaml "*" "@types/yargs-parser@*": version "15.0.0" @@ -13122,7 +13124,7 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@1.9.2, yaml@^1.9.2: +yaml@*, yaml@1.9.2, yaml@^1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== From 0ccb5491ff99f292ff4f906d8e1ee8cdcad71c46 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2020 22:58:21 +0000 Subject: [PATCH 06/12] chore(deps-dev): bump @types/uuid from 7.0.2 to 7.0.3 (#7569) Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 7.0.2 to 7.0.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/aws-cdk/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 6ea62b353060e..9c2bfbef4310c 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -49,7 +49,7 @@ "@types/semver": "^7.1.0", "@types/sinon": "^9.0.0", "@types/table": "^4.0.7", - "@types/uuid": "^7.0.2", + "@types/uuid": "^7.0.3", "@types/yaml": "^1.9.7", "@types/yargs": "^15.0.4", "aws-sdk-mock": "^5.1.0", diff --git a/yarn.lock b/yarn.lock index 12f06a9eec5a2..56806635593fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2342,10 +2342,10 @@ resolved "https://registry.yarnpkg.com/@types/table/-/table-4.0.7.tgz#c21100d37d4924abbbde85414170260d4d7b0316" integrity sha512-HKtXvBxU8U8evZCSlUi9HbfT/SFW7nSGCoiBEheB06jAhXeW6JbGh8biEAqIFG5rZo9f8xeJVdIn455sddmIcw== -"@types/uuid@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.2.tgz#d680a9c596ef84abf5c4c07a32ffd66d582526f8" - integrity sha512-8Ly3zIPTnT0/8RCU6Kg/G3uTICf9sRwYOpUzSIM3503tLIKcnJPRuinHhXngJUy2MntrEf6dlpOHXJju90Qh5w== +"@types/uuid@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.3.tgz#45cd03e98e758f8581c79c535afbd4fc27ba7ac8" + integrity sha512-PUdqTZVrNYTNcIhLHkiaYzoOIaUi5LFg/XLerAdgvwQrUCx+oSbtoBze1AMyvYbcwzUSNC+Isl58SM4Sm/6COw== "@types/yaml@1.9.7", "@types/yaml@^1.9.7": version "1.9.7" From 9566cca8c77b99922e8214567b87fa5680fe06ef Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 24 Apr 2020 11:09:24 +0200 Subject: [PATCH 07/12] fix(cli): can't bootstrap environment not in app It used to be that if we had an `--app` argument, we would always glob arguments to `cdk bootstrap ` through the environments of stacks in the app. This makes it super hard/annoying to run `cdk bootstrap aws://1234/us-somewhere` in a CI/CD project; you have to add a stack there first and compile before you're allowed to do that, which is kinda silly. Change behavior to only glob environment from the environments in the app if it looks like the user is supplying a glob (if it contains `*`). If the user is supplying a concrete environment name, just use it directly. Also in this commit: - refactor: lots of places where we were passing around a pair of `(account, region)`. Replace those by passing a `cxapi.Environment` instead (most of the changes in this PR). - refactor: the old and new bootstrapping experience had a lot of copy/pasta between them. Refactored to make them share code. - (prerelease) feat: add a version check to the bootstrapping operation, so that users won't accidentally downgrade a bootstrap stack to an older version (This happened to 2 devs already, and is an easy mistake to make. Protect against it.) --- packages/@aws-cdk/cx-api/lib/environment.ts | 10 ++ packages/aws-cdk/bin/cdk.ts | 23 ++- .../aws-cdk/lib/api/aws-auth/sdk-provider.ts | 39 ++--- .../aws-cdk/lib/api/bootstrap-environment.ts | 161 ------------------ .../api/bootstrap/bootstrap-environment.ts | 65 +++++++ .../api/bootstrap/bootstrap-environment2.ts | 55 ------ .../lib/api/bootstrap/bootstrap-props.ts | 66 +++++++ .../lib/api/bootstrap/deploy-bootstrap.ts | 61 +++++++ packages/aws-cdk/lib/api/bootstrap/index.ts | 2 + .../lib/api/bootstrap/legacy-template.ts | 65 +++++++ .../lib/api/cloudformation-deployments.ts | 4 +- .../aws-cdk/lib/api/cxapp/environments.ts | 14 +- packages/aws-cdk/lib/api/index.ts | 2 +- packages/aws-cdk/lib/api/toolkit-info.ts | 10 +- packages/aws-cdk/lib/cdk-toolkit.ts | 47 ++--- packages/aws-cdk/lib/context-providers/ami.ts | 2 +- .../context-providers/availability-zones.ts | 3 +- .../lib/context-providers/hosted-zones.ts | 2 +- .../lib/context-providers/ssm-parameters.ts | 3 +- .../aws-cdk/lib/context-providers/vpcs.ts | 2 +- packages/aws-cdk/lib/util/asset-publishing.ts | 9 +- packages/aws-cdk/test/api/bootstrap.test.ts | 44 +++-- packages/aws-cdk/test/api/bootstrap2.test.ts | 43 ++++- .../aws-cdk/test/api/sdk-provider.test.ts | 17 +- packages/aws-cdk/test/cdk-toolkit.test.ts | 64 ++++++- .../integ/bootstrap/bootstrap.integ-test.ts | 28 +-- packages/aws-cdk/test/util/mock-sdk.ts | 1 + packages/cdk-assets/bin/cdk-assets.ts | 2 +- scripts/buildup | 2 +- 29 files changed, 508 insertions(+), 338 deletions(-) delete mode 100644 packages/aws-cdk/lib/api/bootstrap-environment.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts delete mode 100644 packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/index.ts create mode 100644 packages/aws-cdk/lib/api/bootstrap/legacy-template.ts diff --git a/packages/@aws-cdk/cx-api/lib/environment.ts b/packages/@aws-cdk/cx-api/lib/environment.ts index c23e75c7829e3..c5551f673c8e9 100644 --- a/packages/@aws-cdk/cx-api/lib/environment.ts +++ b/packages/@aws-cdk/cx-api/lib/environment.ts @@ -39,6 +39,16 @@ export class EnvironmentUtils { return { account, region, name: environment }; } + /** + * Build an environment object from an account and region + */ + public static make(account: string, region: string): Environment { + return { account, region, name: this.format(account, region) }; + } + + /** + * Format an environment string from an account and region + */ public static format(account: string, region: string): string { return `aws://${account}/${region}`; } diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 8834dbfa08a42..41e8336ced9c0 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -56,7 +56,8 @@ async function parseCommandLineArguments() { .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('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('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 .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times.', default: [] }) @@ -209,14 +210,18 @@ async function initCommandLine() { }); case 'bootstrap': - return await cli.bootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, !!process.env.CDK_NEW_BOOTSTRAP, { - bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), - kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']), - tags: configuration.settings.get(['tags']), - execute: args.execute, - trustedAccounts: args.trust, - cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies, - }); + return await cli.bootstrap(args.ENVIRONMENTS, toolkitStackName, + args.roleArn, + !!process.env.CDK_NEW_BOOTSTRAP, + argv.force, + { + bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']), + kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']), + tags: configuration.settings.get(['tags']), + execute: args.execute, + trustedAccounts: args.trust, + cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies, + }); case 'deploy': const parameterMap: { [name: string]: string | undefined } = {}; 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 22dc175235aeb..98c2e03ac1a62 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -110,11 +110,10 @@ export class SdkProvider { /** * Return an SDK which can do operations in the given environment * - * The `region` and `accountId` parameters are interpreted as in `resolveEnvironment()` (which is to - * say, `undefined` doesn't do what you expect). + * The `environment` parameter is resolved first (see `resolveEnvironment()`). */ - public async forEnvironment(accountId: string | undefined, region: string | undefined, mode: Mode): Promise { - const env = await this.resolveEnvironment(accountId, region); + public async forEnvironment(environment: cxapi.Environment, mode: Mode): Promise { + const env = await this.resolveEnvironment(environment); const creds = await this.obtainCredentials(env.account, mode); return new SDK(creds, env.region, this.sdkOptions); } @@ -150,30 +149,26 @@ export class SdkProvider { /** * Resolve the environment for a stack * - * `undefined` actually means `undefined`, and is NOT changed to default values! Only the magic values UNKNOWN_REGION - * and UNKNOWN_ACCOUNT will be replaced with looked-up values! + * Replaces the magic values `UNKNOWN_REGION` and `UNKNOWN_ACCOUNT` + * with the defaults for the current SDK configuration (`~/.aws/config` or + * otherwise). + * + * It is an error if `UNKNOWN_ACCOUNT` is used but the user hasn't configured + * any SDK credentials. */ - public async resolveEnvironment(accountId: string | undefined, region: string | undefined) { - region = region !== cxapi.UNKNOWN_REGION ? region : this.defaultRegion; - accountId = accountId !== cxapi.UNKNOWN_ACCOUNT ? accountId : (await this.defaultAccount())?.accountId; - - if (!region) { - throw new Error('AWS region must be configured either when you configure your CDK stack or through the environment'); - } + public async resolveEnvironment(env: cxapi.Environment): Promise { + const region = env.region !== cxapi.UNKNOWN_REGION ? env.region : this.defaultRegion; + const account = env.account !== cxapi.UNKNOWN_ACCOUNT ? env.account : (await this.defaultAccount())?.accountId; - if (!accountId) { + if (!account) { throw new Error('Unable to resolve AWS account to use. It must be either configured when you define your CDK or through the environment'); } - const environment: cxapi.Environment = { - region, account: accountId, name: cxapi.EnvironmentUtils.format(accountId, region), + return { + region, + account, + name: cxapi.EnvironmentUtils.format(account, region), }; - - return environment; - } - - public async resolveEnvironmentObject(env: cxapi.Environment) { - return this.resolveEnvironment(env.account, env.region); } /** diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts deleted file mode 100644 index 30a7c3b6cf1ff..0000000000000 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ /dev/null @@ -1,161 +0,0 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs-extra'; -import * as os from 'os'; -import * as path from 'path'; -import { Tag } from '../cdk-toolkit'; -import { Mode, SdkProvider } from './aws-auth'; -import { deployStack, DeployStackResult } from './deploy-stack'; - -// tslint:disable:max-line-length - -/** @experimental */ -export const BUCKET_NAME_OUTPUT = 'BucketName'; -/** @experimental */ -export const REPOSITORY_NAME_OUTPUT = 'RepositoryName'; -/** @experimental */ -export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName'; - -export interface BootstrapEnvironmentProps { - /** - * The name to be given to the CDK Bootstrap bucket. - * - * @default - a name is generated by CloudFormation. - */ - readonly bucketName?: string; - - /** - * The ID of an existing KMS key to be used for encrypting items in the bucket. - * - * @default - the default KMS key for S3 will be used. - */ - readonly kmsKeyId?: string; - /** - * Tags for cdktoolkit stack. - * - * @default - None. - */ - readonly tags?: Tag[]; - /** - * Whether to execute the changeset or only create it and leave it in review. - * @default true - */ - readonly execute?: boolean; - - /** - * The list of AWS account IDs that are trusted to deploy into the environment being bootstrapped. - * - * @default - only the bootstrapped account can deploy into this environment - */ - readonly trustedAccounts?: string[]; - - /** - * The ARNs of the IAM managed policies that should be attached to the role performing CloudFormation deployments. - * In most cases, this will be the AdministratorAccess policy. - * At least one policy is required if {@link trustedAccounts} were passed. - * - * @default - the role will have no policies attached - */ - readonly cloudFormationExecutionPolicies?: string[]; -} - -/** @experimental */ -export async function bootstrapEnvironment(environment: cxapi.Environment, sdkProvider: SdkProvider, toolkitStackName: string, roleArn: string | undefined, props: BootstrapEnvironmentProps = {}): Promise { - if (props.trustedAccounts?.length) { - throw new Error('--trust can only be passed for the new bootstrap experience!'); - } - if (props.cloudFormationExecutionPolicies?.length) { - throw new Error('--cloudformation-execution-policies can only be passed for the new bootstrap experience!'); - } - - const template = { - Description: 'The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.', - Resources: { - StagingBucket: { - Type: 'AWS::S3::Bucket', - Properties: { - BucketName: props.bucketName, - AccessControl: 'Private', - BucketEncryption: { - ServerSideEncryptionConfiguration: [{ - ServerSideEncryptionByDefault: { - SSEAlgorithm: 'aws:kms', - KMSMasterKeyID: props.kmsKeyId, - }, - }], - }, - PublicAccessBlockConfiguration: { - BlockPublicAcls: true, - BlockPublicPolicy: true, - IgnorePublicAcls: true, - RestrictPublicBuckets: true, - }, - }, - }, - StagingBucketPolicy: { - Type: 'AWS::S3::BucketPolicy', - Properties: { - Bucket: { Ref: 'StagingBucket' }, - PolicyDocument: { - Id: 'AccessControl', - Version: '2012-10-17', - Statement: [ - { - Sid: 'AllowSSLRequestsOnly', - Action: 's3:*', - Effect: 'Deny', - Resource: [ - { 'Fn::Sub': '${StagingBucket.Arn}' }, - { 'Fn::Sub': '${StagingBucket.Arn}/*' }, - ], - Condition: { - Bool: { 'aws:SecureTransport': 'false' }, - }, - Principal: '*', - }, - ], - }, - }, - - }, - }, - Outputs: { - [BUCKET_NAME_OUTPUT]: { - Description: 'The name of the S3 bucket owned by the CDK toolkit stack', - Value: { Ref: 'StagingBucket' }, - }, - [BUCKET_DOMAIN_NAME_OUTPUT]: { - Description: 'The domain name of the S3 bucket owned by the CDK toolkit stack', - Value: { 'Fn::GetAtt': ['StagingBucket', 'RegionalDomainName'] }, - }, - }, - }; - - const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap')); - const builder = new cxapi.CloudAssemblyBuilder(outdir); - const templateFile = `${toolkitStackName}.template.json`; - - await fs.writeJson(path.join(builder.outdir, templateFile), template, { spaces: 2 }); - - builder.addArtifact(toolkitStackName, { - type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, - environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), - properties: { - templateFile, - }, - }); - - const assembly = builder.buildAssembly(); - - const resolvedEnvironment = await sdkProvider.resolveEnvironment(environment.account, environment.region); - - return await deployStack({ - stack: assembly.getStackByName(toolkitStackName), - resolvedEnvironment, - sdk: await sdkProvider.forEnvironment(environment.account, environment.region, Mode.ForWriting), - sdkProvider, - roleArn, - tags: props.tags, - execute: props.execute, - }); -} diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts new file mode 100644 index 0000000000000..2bf3e4ede46dd --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts @@ -0,0 +1,65 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as path from 'path'; +import { loadStructuredFile } from '../../serialize'; +import { SdkProvider } from '../aws-auth'; +import { DeployStackResult } from '../deploy-stack'; +import { BootstrapEnvironmentOptions } from './bootstrap-props'; +import { deployBootstrapStack } from './deploy-bootstrap'; +import { legacyBootstrapTemplate } from './legacy-template'; + +// tslint:disable:max-line-length + +/** + * Deploy legacy bootstrap stack + * + * @experimental + */ +export async function bootstrapEnvironment(environment: cxapi.Environment, sdkProvider: SdkProvider, options: BootstrapEnvironmentOptions): Promise { + const params = options.parameters ?? {}; + + if (params.trustedAccounts?.length) { + throw new Error('--trust can only be passed for the new bootstrap experience.'); + } + if (params.cloudFormationExecutionPolicies?.length) { + throw new Error('--cloudformation-execution-policies can only be passed for the new bootstrap experience.'); + } + + return deployBootstrapStack( + legacyBootstrapTemplate(params), + {}, + environment, + sdkProvider, + options); +} + +/** + * Deploy CI/CD-ready bootstrap stack from template + * + * @experimental + */ +export async function bootstrapEnvironment2( + environment: cxapi.Environment, + sdkProvider: SdkProvider, + options: BootstrapEnvironmentOptions): Promise { + + const params = options.parameters ?? {}; + + if (params.trustedAccounts?.length && !params.cloudFormationExecutionPolicies?.length) { + throw new Error('--cloudformation-execution-policies are required if --trust has been passed!'); + } + + const bootstrapTemplatePath = path.join(__dirname, 'bootstrap-template.yaml'); + const bootstrapTemplate = await loadStructuredFile(bootstrapTemplatePath); + + return deployBootstrapStack( + bootstrapTemplate, + { + FileAssetsBucketName: params.bucketName, + FileAssetsBucketKmsKeyId: params.kmsKeyId, + TrustedAccounts: params.trustedAccounts?.join(','), + CloudFormationExecutionPolicies: params.cloudFormationExecutionPolicies?.join(','), + }, + environment, + sdkProvider, + options); +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts deleted file mode 100644 index ec77521b223c6..0000000000000 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs-extra'; -import * as os from 'os'; -import * as path from 'path'; -import { BootstrapEnvironmentProps, deployStack, DeployStackResult } from '..'; -import { loadStructuredFile } from '../../serialize'; -import { Mode, SdkProvider } from '../aws-auth'; - -export async function bootstrapEnvironment2( - environment: cxapi.Environment, sdkProvider: SdkProvider, - toolkitStackName: string, roleArn: string | undefined, - props: BootstrapEnvironmentProps = {}): Promise { - if (props.trustedAccounts?.length && !props.cloudFormationExecutionPolicies?.length) { - throw new Error('--cloudformation-execution-policies are required if --trust has been passed!'); - } - - const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap-new')); - const builder = new cxapi.CloudAssemblyBuilder(outdir); - - // convert from YAML to JSON (which the Cloud Assembly uses) - const templateFile = `${toolkitStackName}.template.json`; - const bootstrapTemplatePath = path.join(__dirname, 'bootstrap-template.yaml'); - const bootstrapTemplateObject = await loadStructuredFile(bootstrapTemplatePath); - await fs.writeJson( - path.join(builder.outdir, templateFile), - bootstrapTemplateObject); - - builder.addArtifact(toolkitStackName, { - type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, - environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), - properties: { - templateFile, - }, - }); - - const resolvedEnvironment = await sdkProvider.resolveEnvironment(environment.account, environment.region); - - const assembly = builder.buildAssembly(); - return await deployStack({ - stack: assembly.getStackByName(toolkitStackName), - resolvedEnvironment, - sdk: await sdkProvider.forEnvironment(environment.account, environment.region, Mode.ForWriting), - sdkProvider, - roleArn, - tags: props.tags, - execute: props.execute, - parameters: { - FileAssetsBucketName: props.bucketName, - FileAssetsBucketKmsKeyId: props.kmsKeyId, - TrustedAccounts: props.trustedAccounts?.join(','), - CloudFormationExecutionPolicies: props.cloudFormationExecutionPolicies?.join(','), - }, - }); -} diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts new file mode 100644 index 0000000000000..2859daba5d308 --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -0,0 +1,66 @@ +import { Tag } from '../../cdk-toolkit'; + +/** @experimental */ +export const BUCKET_NAME_OUTPUT = 'BucketName'; +/** @experimental */ +export const REPOSITORY_NAME_OUTPUT = 'RepositoryName'; +/** @experimental */ +export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName'; +/** @experimental */ +export const BOOTSTRAP_VERSION_OUTPUT = 'BootstrapVersion'; + +/** + * Options for the bootstrapEnvironment operation(s) + */ +export interface BootstrapEnvironmentOptions { + readonly toolkitStackName?: string; + readonly roleArn?: string; + readonly parameters?: BootstrappingParameters; + readonly force?: boolean; +} + +/** + * Parameters for the bootstrapping template + */ +export interface BootstrappingParameters { + /** + * The name to be given to the CDK Bootstrap bucket. + * + * @default - a name is generated by CloudFormation. + */ + readonly bucketName?: string; + + /** + * The ID of an existing KMS key to be used for encrypting items in the bucket. + * + * @default - the default KMS key for S3 will be used. + */ + readonly kmsKeyId?: string; + /** + * Tags for cdktoolkit stack. + * + * @default - None. + */ + readonly tags?: Tag[]; + /** + * Whether to execute the changeset or only create it and leave it in review. + * @default true + */ + readonly execute?: boolean; + + /** + * The list of AWS account IDs that are trusted to deploy into the environment being bootstrapped. + * + * @default - only the bootstrapped account can deploy into this environment + */ + readonly trustedAccounts?: string[]; + + /** + * The ARNs of the IAM managed policies that should be attached to the role performing CloudFormation deployments. + * In most cases, this will be the AdministratorAccess policy. + * At least one policy is required if {@link trustedAccounts} were passed. + * + * @default - the role will have no policies attached + */ + readonly cloudFormationExecutionPolicies?: string[]; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts new file mode 100644 index 0000000000000..8f54b1b665a68 --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -0,0 +1,61 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; +import { Mode, SdkProvider } from '../aws-auth'; +import { deployStack, DeployStackResult } from '../deploy-stack'; +import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; +import { BOOTSTRAP_VERSION_OUTPUT, BootstrapEnvironmentOptions } from './bootstrap-props'; + +/** + * Perform the actual deployment of a bootstrap stack, given a template and some parameters + */ +export async function deployBootstrapStack( + template: any, + parameters: Record, + environment: cxapi.Environment, + sdkProvider: SdkProvider, + options: BootstrapEnvironmentOptions): Promise { + + const toolkitStackName = options.toolkitStackName ?? DEFAULT_TOOLKIT_STACK_NAME; + + const resolvedEnvironment = await sdkProvider.resolveEnvironment(environment); + const sdk = await sdkProvider.forEnvironment(resolvedEnvironment, Mode.ForWriting); + + const newVersion = bootstrapVersionFromTemplate(template); + const currentBootstrapStack = await ToolkitInfo.lookup(resolvedEnvironment, sdk, toolkitStackName); + if (currentBootstrapStack && newVersion < currentBootstrapStack.version && !options.force) { + throw new Error(`Not downgrading existing bootstrap stack from version '${currentBootstrapStack.version}' to version '${newVersion}'. Use --force to force.`); + } + + const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap')); + const builder = new cxapi.CloudAssemblyBuilder(outdir); + const templateFile = `${toolkitStackName}.template.json`; + await fs.writeJson(path.join(builder.outdir, templateFile), template, { spaces: 2 }); + + builder.addArtifact(toolkitStackName, { + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, + environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), + properties: { + templateFile, + }, + }); + + const assembly = builder.buildAssembly(); + + return await deployStack({ + stack: assembly.getStackByName(toolkitStackName), + resolvedEnvironment, + sdk: await sdkProvider.forEnvironment(resolvedEnvironment, Mode.ForWriting), + sdkProvider, + roleArn: options.roleArn, + tags: options.parameters?.tags, + execute: options?.parameters?.execute, + parameters, + }); +} + +function bootstrapVersionFromTemplate(template: any): number { + return parseInt(template.Outputs?.[BOOTSTRAP_VERSION_OUTPUT]?.Value ?? '0', 10); +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/index.ts b/packages/aws-cdk/lib/api/bootstrap/index.ts new file mode 100644 index 0000000000000..8ece35e085f2c --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/index.ts @@ -0,0 +1,2 @@ +export * from './bootstrap-environment'; +export * from './bootstrap-props'; \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts new file mode 100644 index 0000000000000..9bd99a8792836 --- /dev/null +++ b/packages/aws-cdk/lib/api/bootstrap/legacy-template.ts @@ -0,0 +1,65 @@ +import { BootstrappingParameters, BUCKET_DOMAIN_NAME_OUTPUT, BUCKET_NAME_OUTPUT } from './bootstrap-props'; + +export function legacyBootstrapTemplate(params: BootstrappingParameters): any { + return { + Description: 'The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.', + Resources: { + StagingBucket: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: params.bucketName, + AccessControl: 'Private', + BucketEncryption: { + ServerSideEncryptionConfiguration: [{ + ServerSideEncryptionByDefault: { + SSEAlgorithm: 'aws:kms', + KMSMasterKeyID: params.kmsKeyId, + }, + }], + }, + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + }, + }, + StagingBucketPolicy: { + Type: 'AWS::S3::BucketPolicy', + Properties: { + Bucket: { Ref: 'StagingBucket' }, + PolicyDocument: { + Id: 'AccessControl', + Version: '2012-10-17', + Statement: [ + { + Sid: 'AllowSSLRequestsOnly', + Action: 's3:*', + Effect: 'Deny', + Resource: [ + { 'Fn::Sub': '${StagingBucket.Arn}' }, + { 'Fn::Sub': '${StagingBucket.Arn}/*' }, + ], + Condition: { + Bool: { 'aws:SecureTransport': 'false' }, + }, + Principal: '*', + }, + ], + }, + }, + }, + }, + Outputs: { + [BUCKET_NAME_OUTPUT]: { + Description: 'The name of the S3 bucket owned by the CDK toolkit stack', + Value: { Ref: 'StagingBucket' }, + }, + [BUCKET_DOMAIN_NAME_OUTPUT]: { + Description: 'The domain name of the S3 bucket owned by the CDK toolkit stack', + Value: { 'Fn::GetAtt': ['StagingBucket', 'RegionalDomainName'] }, + }, + }, + }; +} \ No newline at end of file diff --git a/packages/aws-cdk/lib/api/cloudformation-deployments.ts b/packages/aws-cdk/lib/api/cloudformation-deployments.ts index dbacbecff42c1..65dc93616b4d6 100644 --- a/packages/aws-cdk/lib/api/cloudformation-deployments.ts +++ b/packages/aws-cdk/lib/api/cloudformation-deployments.ts @@ -172,9 +172,9 @@ export class CloudFormationDeployments { if (!stack.environment) { throw new Error(`The stack ${stack.displayName} does not have an environment`); } - const resolvedEnvironment = await this.sdkProvider.resolveEnvironment(stack.environment.account, stack.environment.region); + const resolvedEnvironment = await this.sdkProvider.resolveEnvironment(stack.environment); - const stackSdk = await this.sdkProvider.forEnvironment(stack.environment.account, stack.environment.region, mode); + const stackSdk = await this.sdkProvider.forEnvironment(stack.environment, mode); return { stackSdk, diff --git a/packages/aws-cdk/lib/api/cxapp/environments.ts b/packages/aws-cdk/lib/api/cxapp/environments.ts index 0dc3ad65e611f..d95d44b3ea38e 100644 --- a/packages/aws-cdk/lib/api/cxapp/environments.ts +++ b/packages/aws-cdk/lib/api/cxapp/environments.ts @@ -3,15 +3,17 @@ import * as minimatch from 'minimatch'; import { SdkProvider } from '../aws-auth'; import { StackCollection } from './cloud-assembly'; +export function looksLikeGlob(environment: string) { + return environment.indexOf('*') > -1; +} + // tslint:disable-next-line:max-line-length export async function globEnvironmentsFromStacks(stacks: StackCollection, environmentGlobs: string[], sdk: SdkProvider): Promise { - if (environmentGlobs.length === 0) { - environmentGlobs = [ '**' ]; // default to ALL - } + if (environmentGlobs.length === 0) { return []; } const availableEnvironments = new Array(); for (const stack of stacks.stackArtifacts) { - const actual = await sdk.resolveEnvironment(stack.environment.account, stack.environment.region); + const actual = await sdk.resolveEnvironment(stack.environment); availableEnvironments.push(actual); } @@ -29,10 +31,6 @@ export async function globEnvironmentsFromStacks(stacks: StackCollection, enviro * Given a set of "/" strings, construct environments for them */ export function environmentsFromDescriptors(envSpecs: string[]): cxapi.Environment[] { - if (envSpecs.length === 0) { - throw new Error('Either specify an app with \'--app\', or specify an environment name like \'aws://123456789012/us-east-1\''); - } - const ret = new Array(); for (const spec of envSpecs) { diff --git a/packages/aws-cdk/lib/api/index.ts b/packages/aws-cdk/lib/api/index.ts index 702bfc5a1a721..5747b4c8d570f 100644 --- a/packages/aws-cdk/lib/api/index.ts +++ b/packages/aws-cdk/lib/api/index.ts @@ -1,7 +1,7 @@ import 'source-map-support/register'; export * from './aws-auth/credentials'; -export * from './bootstrap-environment'; +export * from './bootstrap'; export * from './deploy-stack'; export * from './toolkit-info'; export * from './aws-auth'; diff --git a/packages/aws-cdk/lib/api/toolkit-info.ts b/packages/aws-cdk/lib/api/toolkit-info.ts index e0276465d6214..d695547a571e0 100644 --- a/packages/aws-cdk/lib/api/toolkit-info.ts +++ b/packages/aws-cdk/lib/api/toolkit-info.ts @@ -2,7 +2,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import { debug } from '../logging'; import { ISDK } from './aws-auth'; -import { BUCKET_DOMAIN_NAME_OUTPUT, BUCKET_NAME_OUTPUT } from './bootstrap-environment'; +import { BOOTSTRAP_VERSION_OUTPUT, BUCKET_DOMAIN_NAME_OUTPUT, BUCKET_NAME_OUTPUT } from './bootstrap'; import { waitForStack } from './util/cloudformation'; export const DEFAULT_TOOLKIT_STACK_NAME = 'CDKToolkit'; @@ -29,6 +29,7 @@ export class ToolkitInfo { sdk, environment, bucketName: requireOutput(BUCKET_NAME_OUTPUT), bucketEndpoint: requireOutput(BUCKET_DOMAIN_NAME_OUTPUT), + version: parseInt(outputs[BOOTSTRAP_VERSION_OUTPUT] ?? '0', 10), }); function requireOutput(output: string): string { @@ -45,7 +46,8 @@ export class ToolkitInfo { readonly sdk: ISDK, bucketName: string, bucketEndpoint: string, - environment: cxapi.Environment + environment: cxapi.Environment, + version: number, }) { this.sdk = props.sdk; } @@ -58,6 +60,10 @@ export class ToolkitInfo { return this.props.bucketName; } + public get version() { + return this.props.version; + } + /** * Prepare an ECR repository for uploading to using Docker * diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 2ef3591a089d3..6028c2b6fea28 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -5,10 +5,10 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as promptly from 'promptly'; import { format } from 'util'; -import { bootstrapEnvironment, BootstrapEnvironmentProps } from '../lib'; -import { environmentsFromDescriptors, globEnvironmentsFromStacks } from '../lib/api/cxapp/environments'; +import { environmentsFromDescriptors, globEnvironmentsFromStacks, looksLikeGlob } from '../lib/api/cxapp/environments'; +import { bootstrapEnvironment } from './api'; import { SdkProvider } from './api/aws-auth'; -import { bootstrapEnvironment2 } from './api/bootstrap/bootstrap-environment2'; +import { bootstrapEnvironment2, BootstrappingParameters } from './api/bootstrap'; import { CloudFormationDeployments } from './api/cloudformation-deployments'; import { CloudAssembly, DefaultSelection, ExtendedStackSelection, StackCollection } from './api/cxapp/cloud-assembly'; import { CloudExecutable } from './api/cxapp/cloud-executable'; @@ -16,6 +16,7 @@ import { printSecurityDiff, printStackDiff, RequireApproval } from './diff'; import { data, error, highlight, print, success, warning } from './logging'; import { deserializeStructure } from './serialize'; import { Configuration } from './settings'; +import { partition } from './util'; export interface CdkToolkitProps { @@ -322,32 +323,40 @@ export class CdkToolkit { /** * Bootstrap the CDK Toolkit stack in the accounts used by the specified stack(s). * - * @param environmentGlobs environment names that need to have toolkit support + * @param environmentSpecs environment names that need to have toolkit support * provisioned, as a glob filter. If none is provided, * all stacks are implicitly selected. * @param toolkitStackName the name to be used for the CDK Toolkit stack. */ public async bootstrap( - environmentGlobs: string[], toolkitStackName: string, roleArn: string | undefined, - useNewBootstrapping: boolean, props: BootstrapEnvironmentProps): Promise { - // Two modes of operation. - // - // If there is an '--app' argument, we select the environments from the app. Otherwise we just take the user - // at their word that they know the name of the environment. - let environments: cxapi.Environment[]; - if (this.props.cloudExecutable.hasApp) { - const stacks = await this.selectStacksForList([]); - environments = await globEnvironmentsFromStacks(stacks, environmentGlobs, this.props.sdkProvider); - } else { - environments = environmentsFromDescriptors(environmentGlobs); + environmentSpecs: string[], toolkitStackName: string | undefined, roleArn: string | undefined, + useNewBootstrapping: boolean, force: boolean | undefined, props: BootstrappingParameters): Promise { + // If there is an '--app' argument and an environment looks like a glob, we + // select the environments from the app. Otherwise use what the user said. + + // By default glob for everything + environmentSpecs = environmentSpecs.length > 0 ? environmentSpecs : ['**']; + + // Partition into globs and non-globs (this will mutate environmentSpecs). + const globSpecs = partition(environmentSpecs, looksLikeGlob); + if (globSpecs.length > 0 && !this.props.cloudExecutable.hasApp) { + throw new Error(`'${globSpecs}' is not an environment name. Run in app directory to glob or specify an environment name like \'aws://123456789012/us-east-1\'.`); } + const environments: cxapi.Environment[] = [ + ...environmentsFromDescriptors(environmentSpecs), + ...await globEnvironmentsFromStacks(await this.selectStacksForList([]), globSpecs, this.props.sdkProvider), + ]; + await Promise.all(environments.map(async (environment) => { success(' ⏳ Bootstrapping environment %s...', colors.blue(environment.name)); try { - const result = useNewBootstrapping - ? await bootstrapEnvironment2(environment, this.props.sdkProvider, toolkitStackName, roleArn, props) - : await bootstrapEnvironment(environment, this.props.sdkProvider, toolkitStackName, roleArn, props); + const result = await (useNewBootstrapping ? bootstrapEnvironment2 : bootstrapEnvironment)(environment, this.props.sdkProvider, { + toolkitStackName, + roleArn, + force, + parameters: props, + }); const message = result.noOp ? ' ✅ Environment %s bootstrapped (no changes).' : ' ✅ Environment %s bootstrapped.'; diff --git a/packages/aws-cdk/lib/context-providers/ami.ts b/packages/aws-cdk/lib/context-providers/ami.ts index 5b930b8985553..0cd461df4df43 100644 --- a/packages/aws-cdk/lib/context-providers/ami.ts +++ b/packages/aws-cdk/lib/context-providers/ami.ts @@ -19,7 +19,7 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin { print(`Searching for AMI in ${account}:${region}`); debug(`AMI search parameters: ${JSON.stringify(args)}`); - const ec2 = (await this.aws.forEnvironment(account, region, Mode.ForReading)).ec2(); + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); const response = await ec2.describeImages({ Owners: args.owners, Filters: Object.entries(args.filters).map(([key, values]) => ({ diff --git a/packages/aws-cdk/lib/context-providers/availability-zones.ts b/packages/aws-cdk/lib/context-providers/availability-zones.ts index c7482c6977ffb..b5b4cd6296fb4 100644 --- a/packages/aws-cdk/lib/context-providers/availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/availability-zones.ts @@ -1,3 +1,4 @@ +import * as cxapi from '@aws-cdk/cx-api'; import { Mode, SdkProvider } from '../api'; import { debug } from '../logging'; import { ContextProviderPlugin } from './provider'; @@ -13,7 +14,7 @@ export class AZContextProviderPlugin implements ContextProviderPlugin { const region = args.region; const account = args.account; debug(`Reading AZs for ${account}:${region}`); - const ec2 = (await this.aws.forEnvironment(account, region, Mode.ForReading)).ec2(); + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); const response = await ec2.describeAvailabilityZones().promise(); if (!response.AvailabilityZones) { return []; } const azs = response.AvailabilityZones.filter(zone => zone.State === 'available').map(zone => zone.ZoneName); diff --git a/packages/aws-cdk/lib/context-providers/hosted-zones.ts b/packages/aws-cdk/lib/context-providers/hosted-zones.ts index 3d77e56a4de88..bb61c181c2263 100644 --- a/packages/aws-cdk/lib/context-providers/hosted-zones.ts +++ b/packages/aws-cdk/lib/context-providers/hosted-zones.ts @@ -16,7 +16,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { } const domainName = args.domainName; debug(`Reading hosted zone ${account}:${region}:${domainName}`); - const r53 = (await this.aws.forEnvironment(account, region, Mode.ForReading)).route53(); + const r53 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).route53(); const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); if (!response.HostedZones) { throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); diff --git a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts index d92be3101b9c2..25fb21215e1fb 100644 --- a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts +++ b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts @@ -1,3 +1,4 @@ +import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; import { Mode, SdkProvider } from '../api'; import { debug } from '../logging'; @@ -37,7 +38,7 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { * @throws Error if a service error (other than ``ParameterNotFound``) occurs. */ private async getSsmParameterValue(account: string, region: string, parameterName: string): Promise { - const ssm = (await this.aws.forEnvironment(account, region, Mode.ForReading)).ssm(); + const ssm = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ssm(); try { return await ssm.getParameter({ Name: parameterName }).promise(); } catch (e) { diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts index aca46c668278f..058e81b6c8b18 100644 --- a/packages/aws-cdk/lib/context-providers/vpcs.ts +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -13,7 +13,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { const account: string = args.account!; const region: string = args.region!; - const ec2 = (await this.aws.forEnvironment(account, region, Mode.ForReading)).ec2(); + const ec2 = (await this.aws.forEnvironment(cxapi.EnvironmentUtils.make(account, region), Mode.ForReading)).ec2(); const vpcId = await this.findVpc(ec2, args); diff --git a/packages/aws-cdk/lib/util/asset-publishing.ts b/packages/aws-cdk/lib/util/asset-publishing.ts index 41a0026aa0cf5..9bfa8dea5b694 100644 --- a/packages/aws-cdk/lib/util/asset-publishing.ts +++ b/packages/aws-cdk/lib/util/asset-publishing.ts @@ -59,11 +59,14 @@ class PublishingAws implements cdk_assets.IAws { * Get an SDK appropriate for the given client options */ private sdk(options: cdk_assets.ClientOptions): Promise { - const region = options.region ?? this.targetEnv.region; // Default: same region as the stack + const env = { + ...this.targetEnv, + region: options.region ?? this.targetEnv.region, // Default: same region as the stack + }; return options.assumeRoleArn - ? this.aws.withAssumedRole(options.assumeRoleArn, options.assumeRoleExternalId, region) - : this.aws.forEnvironment(this.targetEnv.account, region, Mode.ForWriting); + ? this.aws.withAssumedRole(options.assumeRoleArn, options.assumeRoleExternalId, env.region) + : this.aws.forEnvironment(env, Mode.ForWriting); } } diff --git a/packages/aws-cdk/test/api/bootstrap.test.ts b/packages/aws-cdk/test/api/bootstrap.test.ts index bc4ef75f49f99..560552236ec71 100644 --- a/packages/aws-cdk/test/api/bootstrap.test.ts +++ b/packages/aws-cdk/test/api/bootstrap.test.ts @@ -19,7 +19,8 @@ beforeEach(() => { cfnMocks = { describeStacks: jest.fn() - // First call, no stacks exist + // First two calls, no stacks exist (first is for version checking, second is in deploy-stack.ts) + .mockImplementationOnce(() => ({ Stacks: [] })) .mockImplementationOnce(() => ({ Stacks: [] })) // Second call, stack has been created .mockImplementationOnce(() => ({ Stacks: [ @@ -46,7 +47,7 @@ beforeEach(() => { test('do bootstrap', async () => { // WHEN - const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined); + const ret = await bootstrapEnvironment(env, sdk, { toolkitStackName: 'mockStack' }); // THEN const bucketProperties = changeSetTemplate.Resources.StagingBucket.Properties; @@ -59,8 +60,11 @@ test('do bootstrap', async () => { test('do bootstrap using custom bucket name', async () => { // WHEN - const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { - bucketName: 'foobar', + const ret = await bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + bucketName: 'foobar', + }, }); // THEN @@ -74,8 +78,11 @@ test('do bootstrap using custom bucket name', async () => { test('do bootstrap using KMS CMK', async () => { // WHEN - const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { - kmsKeyId: 'myKmsKey', + const ret = await bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + kmsKeyId: 'myKmsKey', + }, }); // THEN @@ -89,8 +96,11 @@ test('do bootstrap using KMS CMK', async () => { test('do bootstrap with custom tags for toolkit stack', async () => { // WHEN - const ret = await bootstrapEnvironment(env, sdk, 'mockStack', undefined, { - tags: [{ Key: 'Foo', Value: 'Bar' }], + const ret = await bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + tags: [{ Key: 'Foo', Value: 'Bar' }], + }, }); // THEN @@ -103,17 +113,23 @@ test('do bootstrap with custom tags for toolkit stack', async () => { }); test('passing trusted accounts to the old bootstrapping results in an error', async () => { - await expect(bootstrapEnvironment(env, sdk, 'mockStack', undefined, { - trustedAccounts: ['0123456789012'], + await expect(bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + trustedAccounts: ['0123456789012'], + }, })) .rejects - .toThrow('--trust can only be passed for the new bootstrap experience!'); + .toThrow('--trust can only be passed for the new bootstrap experience.'); }); test('passing CFN execution policies to the old bootstrapping results in an error', async () => { - await expect(bootstrapEnvironment(env, sdk, 'mockStack', undefined, { - cloudFormationExecutionPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], + await expect(bootstrapEnvironment(env, sdk, { + toolkitStackName: 'mockStack', + parameters: { + cloudFormationExecutionPolicies: ['arn:aws:iam::aws:policy/AdministratorAccess'], + }, })) .rejects - .toThrow('--cloudformation-execution-policies can only be passed for the new bootstrap experience!'); + .toThrow('--cloudformation-execution-policies can only be passed for the new bootstrap experience.'); }); diff --git a/packages/aws-cdk/test/api/bootstrap2.test.ts b/packages/aws-cdk/test/api/bootstrap2.test.ts index 84d068a23f413..f90db81806d24 100644 --- a/packages/aws-cdk/test/api/bootstrap2.test.ts +++ b/packages/aws-cdk/test/api/bootstrap2.test.ts @@ -4,7 +4,17 @@ jest.mock('../../lib/api/deploy-stack', () => ({ deployStack: mockDeployStack, })); -import { bootstrapEnvironment2 } from '../../lib/api/bootstrap/bootstrap-environment2'; +let mockToolkitInfo: any; + +jest.mock('../../lib/api/toolkit-info', () => ({ + // Pretend there's no toolkit deployed yet + DEFAULT_TOOLKIT_STACK_NAME: 'CDKToolkit', + ToolkitInfo: { + lookup: () => mockToolkitInfo, + }, +})); + +import { bootstrapEnvironment2 } from '../../lib/api/bootstrap'; import { MockSdkProvider } from '../util/mock-sdk'; describe('Bootstrapping v2', () => { @@ -14,10 +24,13 @@ describe('Bootstrapping v2', () => { name: 'mock', }; const sdk = new MockSdkProvider(); + mockToolkitInfo = undefined; test('passes the bucket name as a CFN parameter', async () => { - await bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { - bucketName: 'my-bucket-name', + await bootstrapEnvironment2(env, sdk, { + parameters: { + bucketName: 'my-bucket-name', + }, }); expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ @@ -28,8 +41,10 @@ describe('Bootstrapping v2', () => { }); test('passes the KMS key ID as a CFN parameter', async () => { - await bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { - kmsKeyId: 'my-kms-key-id', + await bootstrapEnvironment2(env, sdk, { + parameters: { + kmsKeyId: 'my-kms-key-id', + }, }); expect(mockDeployStack).toHaveBeenCalledWith(expect.objectContaining({ @@ -40,14 +55,26 @@ describe('Bootstrapping v2', () => { }); test('passing trusted accounts without CFN managed policies results in an error', async () => { - await expect(bootstrapEnvironment2(env, sdk, 'mockStack', undefined, { - trustedAccounts: ['123456789012'], + await expect(bootstrapEnvironment2(env, sdk, { + parameters: { + trustedAccounts: ['123456789012'], + }, })) .rejects .toThrow('--cloudformation-execution-policies are required if --trust has been passed!'); }); + test('Do not allow downgrading bootstrap stack version', async () => { + // GIVEN + mockToolkitInfo = { + version: 999, + }; + + await expect(bootstrapEnvironment2(env, sdk, {})) + .rejects.toThrow('Not downgrading existing bootstrap stack'); + }); + afterEach(() => { mockDeployStack.mockClear(); }); -}); +}); \ No newline at end of file diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index c1daa73312aae..21bb61dfbb9ed 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -21,6 +21,7 @@ const defaultCredOptions = { // Account cache buster let uid: string; let pluginQueried = false; +let defaultEnv: cxapi.Environment; beforeEach(() => { uid = `(${uuid.v4()})`; @@ -83,6 +84,8 @@ beforeEach(() => { name: 'test plugin', }); + defaultEnv = cxapi.EnvironmentUtils.make(`${uid}the_account_#`, 'def'); + // Set environment variables that we want process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); @@ -107,7 +110,7 @@ describe('CLI compatible credentials loading', () => { // THEN expect(provider.defaultRegion).toEqual('eu-bla-5'); await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(`${uid}the_account_#`, 'rgn', Mode.ForReading); + const sdk = await provider.forEnvironment({ ...defaultEnv, region: 'rgn' }, Mode.ForReading); expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); expect(sdkConfig(sdk).region).toEqual('rgn'); }); @@ -117,7 +120,7 @@ describe('CLI compatible credentials loading', () => { const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); // THEN - const sdk = await provider.forEnvironment(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION, Mode.ForReading); + const sdk = await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), Mode.ForReading); expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); expect(sdkConfig(sdk).region).toEqual('eu-bla-5'); }); @@ -129,7 +132,7 @@ describe('CLI compatible credentials loading', () => { // THEN expect(provider.defaultRegion).toEqual('eu-west-1'); await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(`${uid}the_account_#`, 'def', Mode.ForReading); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}fooccess`); }); @@ -140,14 +143,14 @@ describe('CLI compatible credentials loading', () => { // THEN expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(`${uid}the_account_#`, 'def', Mode.ForReading); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}booccess`); }); test('different account throws', async () => { const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); - await expect(provider.forEnvironment(`${uid}some_account_#`, 'def', Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); + await expect(provider.forEnvironment({...defaultEnv, account: `${uid}some_account_#` }, Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); }); test('even when using a profile to assume another profile, STS calls goes through the proxy', async () => { @@ -191,13 +194,13 @@ describe('CLI compatible credentials loading', () => { describe('Plugins', () => { test('does not use plugins if current credentials are for expected account', async () => { const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment(`${uid}the_account_#`, 'def', Mode.ForReading); + await provider.forEnvironment(defaultEnv, Mode.ForReading); expect(pluginQueried).toEqual(false); }); test('uses plugin for other account', async () => { const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment(`${uid}plugin_account_#`, 'def', Mode.ForReading); + await provider.forEnvironment({...defaultEnv, account: `${uid}plugin_account_#`}, Mode.ForReading); expect(pluginQueried).toEqual(true); }); }); diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index c97a24ac7ea1c..239745c7483a1 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -1,3 +1,10 @@ +const mockBootstrapEnvironment = jest.fn(); +jest.mock('../lib/api/bootstrap', () => { + return { + bootstrapEnvironment: mockBootstrapEnvironment, + }; +}); + import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { CloudFormationDeployments, DeployStackOptions } from '../lib/api/cloudformation-deployments'; @@ -14,21 +21,27 @@ beforeEach(() => { MockStack.MOCK_STACK_B, ], }); + + mockBootstrapEnvironment.mockReset().mockResolvedValue({ noOp: false, outputs: {} }); }); +function defaultToolkitSetup() { + return new CdkToolkit({ + cloudExecutable, + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + cloudFormation: new FakeCloudFormation({ + 'Test-Stack-A': { Foo: 'Bar' }, + 'Test-Stack-B': { Baz: 'Zinga!' }, + }), + }); +} + describe('deploy', () => { describe('makes correct CloudFormation calls', () => { test('without options', async () => { // GIVEN - const toolkit = new CdkToolkit({ - cloudExecutable, - configuration: cloudExecutable.configuration, - sdkProvider: cloudExecutable.sdkProvider, - cloudFormation: new FakeCloudFormation({ - 'Test-Stack-A': { Foo: 'Bar' }, - 'Test-Stack-B': { Baz: 'Zinga!' }, - }), - }); + const toolkit = defaultToolkitSetup(); // WHEN await toolkit.deploy({ stackNames: ['Test-Stack-A', 'Test-Stack-B'] }); @@ -53,6 +66,39 @@ describe('deploy', () => { notificationArns, }); }); + + test('globless bootstrap uses environment without question', async () => { + // GIVEN + const toolkit = defaultToolkitSetup(); + + // WHEN + await toolkit.bootstrap(['aws://56789/south-pole'], undefined, undefined, false, false, {}); + + // THEN + expect(mockBootstrapEnvironment).toHaveBeenCalledWith({ + account: '56789', + region: 'south-pole', + name: 'aws://56789/south-pole', + }, expect.anything(), expect.anything()); + expect(mockBootstrapEnvironment).toHaveBeenCalledTimes(1); + }); + + test('globby bootstrap uses whats in the stacks', async () => { + // GIVEN + const toolkit = defaultToolkitSetup(); + cloudExecutable.configuration.settings.set(['app'], 'something'); + + // WHEN + await toolkit.bootstrap(['aws://*/bermuda-triangle-1'], undefined, undefined, false, false, {}); + + // THEN + expect(mockBootstrapEnvironment).toHaveBeenCalledWith({ + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, expect.anything(), expect.anything()); + expect(mockBootstrapEnvironment).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/aws-cdk/test/integ/bootstrap/bootstrap.integ-test.ts b/packages/aws-cdk/test/integ/bootstrap/bootstrap.integ-test.ts index 654ff48eb0f9e..33b74253a9292 100644 --- a/packages/aws-cdk/test/integ/bootstrap/bootstrap.integ-test.ts +++ b/packages/aws-cdk/test/integ/bootstrap/bootstrap.integ-test.ts @@ -4,7 +4,7 @@ import * as AWS from 'aws-sdk'; import * as fs from 'fs-extra'; import * as path from 'path'; import { bootstrapEnvironment, deployStack, destroyStack, ISDK, Mode, SdkProvider, ToolkitInfo } from '../../../lib/api'; -import { bootstrapEnvironment2 } from '../../../lib/api/bootstrap/bootstrap-environment2'; +import { bootstrapEnvironment2 } from '../../../lib/api/bootstrap'; import { ExampleAsset, MyTestCdkStack } from './example-cdk-app/my-test-cdk-stack'; jest.setTimeout(600_000); @@ -30,7 +30,7 @@ describe('Bootstrapping', () => { userAgent: 'aws-cdk-bootstrap-integ-test', }, }); - sdk = await sdkProvider.forEnvironment(env.account, env.region, Mode.ForWriting); + sdk = await sdkProvider.forEnvironment(env, Mode.ForWriting); s3 = sdk.s3(); }); @@ -45,8 +45,11 @@ describe('Bootstrapping', () => { beforeAll(async () => { // bootstrap the "old" way - const bootstrapResults = await bootstrapEnvironment(env, sdkProvider, bootstrapStackName, undefined, { - bucketName: legacyBootstrapBucketName, + const bootstrapResults = await bootstrapEnvironment(env, sdkProvider, { + toolkitStackName: bootstrapStackName, + parameters: { + bucketName: legacyBootstrapBucketName, + }, }); bootstrapStack = bootstrapResults.stackArtifact; }); @@ -63,13 +66,16 @@ describe('Bootstrapping', () => { describe('and then updates the bootstrap stack with the new resources', () => { beforeAll(async () => { // bootstrap the "new" way - const bootstrapResults = await bootstrapEnvironment2(env, sdkProvider, bootstrapStackName, undefined, { - bucketName: newBootstrapBucketName, - trustedAccounts: ['790124522186', '593667001225'], - cloudFormationExecutionPolicies: [ - 'arn:aws:iam::aws:policy/AdministratorAccess', - 'arn:aws:iam::aws:policy/AmazonS3FullAccess', - ], + const bootstrapResults = await bootstrapEnvironment2(env, sdkProvider, { + toolkitStackName: bootstrapStackName, + parameters: { + bucketName: newBootstrapBucketName, + trustedAccounts: ['790124522186', '593667001225'], + cloudFormationExecutionPolicies: [ + 'arn:aws:iam::aws:policy/AdministratorAccess', + 'arn:aws:iam::aws:policy/AmazonS3FullAccess', + ], + }, }); bootstrapStack = bootstrapResults.stackArtifact; }); diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index c773d3de0d5b8..74a7a76786025 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -170,6 +170,7 @@ export function mockToolkitInfo() { bucketName: 'BUCKET_NAME', bucketEndpoint: 'BUCKET_ENDPOINT', environment: { name: 'env', account: '1234', region: 'abc' }, + version: 1, }); } diff --git a/packages/cdk-assets/bin/cdk-assets.ts b/packages/cdk-assets/bin/cdk-assets.ts index ff837fd6dabe5..902c4d65ecc41 100644 --- a/packages/cdk-assets/bin/cdk-assets.ts +++ b/packages/cdk-assets/bin/cdk-assets.ts @@ -64,4 +64,4 @@ main().catch(e => { // tslint:disable-next-line:no-console console.error(e.stack); process.exitCode = 1; -}); \ No newline at end of file +}); diff --git a/scripts/buildup b/scripts/buildup index 3fea629af9976..59aeff93dcf50 100755 --- a/scripts/buildup +++ b/scripts/buildup @@ -19,7 +19,7 @@ else fi fi -${scriptdir}/foreach.sh --up yarn build +${scriptdir}/foreach.sh --up yarn build+test ${scriptdir}/foreach.sh --reset echo "************************************************************" From a60490991c8c49bc3dc2c62dc617bce0b808c5db Mon Sep 17 00:00:00 2001 From: AWS CDK Team Date: Thu, 23 Apr 2020 12:25:19 +0000 Subject: [PATCH 08/12] chore(release): 1.35.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ lerna.json | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fddbe75d0bad1..af8b4bfe02d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ 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.35.0](https://github.com/aws/aws-cdk/compare/v1.34.1...v1.35.0) (2020-04-23) + + +### ⚠ BREAKING CHANGES + +* **assets:** `cdk deploy` now needs `s3:ListBucket` instead of `s3:HeadObject`. +* **efs:** Exported types no longer have the `Efs` prefix. +* **efs:** `provisionedThroughputInMibps` property is renamed to `provisionedThroughputPerSecond` and has the type `Size`. +* **efs:** The property `fileSystemID` is now renamed to `fileSystemId` in the now named `FileSystemAttributes` (previously, `EfsFileSystemAttributes`). +* **efs:** `LifecyclePolicyProperty` is now renamed to `LifecyclePolicy`. + +### Features + +* **cfnspec:** cloudformation spec v13.0.0 ([#7504](https://github.com/aws/aws-cdk/issues/7504)) ([6903869](https://github.com/aws/aws-cdk/commit/6903869def944f8100c8eef51dd7145c181984e2)) +* **cloudtrail:** Lambda Function data events ([4a70138](https://github.com/aws/aws-cdk/commit/4a70138faf2e863be37a66bec23ed29a784b486a)) +* **cognito:** user pool domain ([#7224](https://github.com/aws/aws-cdk/issues/7224)) ([feadd6c](https://github.com/aws/aws-cdk/commit/feadd6cb643b415ae002191ba2cb4622221a5af6)), closes [#6787](https://github.com/aws/aws-cdk/issues/6787) +* **stepfunctions:** retrieve all reachable states from a given state in a state machine definition ([#7324](https://github.com/aws/aws-cdk/issues/7324)) ([ac3b330](https://github.com/aws/aws-cdk/commit/ac3b330c71ef258afd145b86fd90a06db5d1c990)), closes [#7256](https://github.com/aws/aws-cdk/issues/7256) + + +### Bug Fixes + +* **assets:** infrequent "ValidationError: S3 error: Access Denied" ([#7556](https://github.com/aws/aws-cdk/issues/7556)) ([00c9deb](https://github.com/aws/aws-cdk/commit/00c9deb975fe794eef9003cd26a6453abc514928)), closes [#6430](https://github.com/aws/aws-cdk/issues/6430) [#7553](https://github.com/aws/aws-cdk/issues/7553) +* **route53:** cannot add tags to `HostedZone` ([#7531](https://github.com/aws/aws-cdk/issues/7531)) ([2729804](https://github.com/aws/aws-cdk/commit/272980492dc6b98d71ce9c3b23cab38f656dc632)), closes [#7445](https://github.com/aws/aws-cdk/issues/7445) + + +* **efs:** drop Efs prefix from all exported types ([#7481](https://github.com/aws/aws-cdk/issues/7481)) ([ddd47cd](https://github.com/aws/aws-cdk/commit/ddd47cd7e0735424d2e47891c32e4b7813035067)) + ## [1.34.1](https://github.com/aws/aws-cdk/compare/v1.34.0...v1.34.1) (2020-04-22) diff --git a/lerna.json b/lerna.json index 83473fa4f25e3..539c1710776a4 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.34.1" + "version": "1.35.0" } From 66794e934e4cc1352c783abaf2335805656f5a37 Mon Sep 17 00:00:00 2001 From: Sachin Shekhar Date: Fri, 24 Apr 2020 17:12:47 +0530 Subject: [PATCH 09/12] chore(apigateway): updates jsdocs of some methodOptions Updates jsdocs of requestModels, requestValidator and requestValidatorOptions of aws-apigateway.MethodOptions. Example and link have also been included in the jsdocs of requestModels. --- .../@aws-cdk/aws-apigateway/lib/method.ts | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 87f3b047bc5d4..a459c4265a8d4 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -52,7 +52,7 @@ export interface MethodOptions { * for the integration response to be correctly mapped to a response to the client. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-settings-method-response.html */ - readonly methodResponses?: MethodResponse[] + readonly methodResponses?: MethodResponse[]; /** * The request parameters that API Gateway accepts. Specify request parameters @@ -65,15 +65,45 @@ export interface MethodOptions { readonly requestParameters?: { [param: string]: boolean }; /** - * The resources that are used for the response's content type. Specify request - * models as key-value pairs (string-to-string mapping), with a content type - * as the key and a Model resource name as the value + * The models which describe data structure of request payload. When + * combined with `requestValidator` or `requestValidatorOptions`, the service + * will validate the API request payload before it reaches the API's Integration (including proxies). + * Specify `requestModels` as key-value pairs, with a content type + * (e.g. `'application/json'`) as the key and an API Gateway Model as the value. + * + * @example + * + * const userModel: apigateway.Model = api.addModel('UserModel', { + * schema: { + * type: apigateway.JsonSchemaType.OBJECT + * properties: { + * userId: { + * type: apigateway.JsonSchema.STRING + * }, + * name: { + * type: apigateway.JsonSchema.STRING + * } + * }, + * required: ['userId'] + * } + * }); + * api.root.addResource('user').addMethod('POST', + * new apigateway.LambdaIntegration(userLambda), { + * requestModels: { + * 'application/json': userModel + * } + * } + * ); + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-settings-method-request.html#setup-method-request-model */ readonly requestModels?: { [param: string]: IModel }; /** * The ID of the associated request validator. * Only one of `requestValidator` or `requestValidatorOptions` must be specified. + * Works together with `requestModels` or `requestParameters` to validate + * the request before it reaches integration like Lambda Proxy Integration. * @default - No default validator */ readonly requestValidator?: IRequestValidator; @@ -84,11 +114,13 @@ export interface MethodOptions { * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html#cfn-apigateway-method-authorizationscopes * @default - no authorization scopes */ - readonly authorizationScopes?: string[] + readonly authorizationScopes?: string[]; /** * Request validator options to create new validator * Only one of `requestValidator` or `requestValidatorOptions` must be specified. + * Works together with `requestModels` or `requestParameters` to validate + * the request before it reaches integration like Lambda Proxy Integration. * @default - No default validator */ readonly requestValidatorOptions?: RequestValidatorOptions; From fb51c4b62c90ce0171386a3a06559d98c5fca4dc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2020 15:51:07 +0000 Subject: [PATCH 10/12] chore(deps): bump source-map-support from 0.5.18 to 0.5.19 (#7593) Bumps [source-map-support](https://github.com/evanw/node-source-map-support) from 0.5.18 to 0.5.19. - [Release notes](https://github.com/evanw/node-source-map-support/releases) - [Commits](https://github.com/evanw/node-source-map-support/compare/v0.5.18...v0.5.19) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/aws-cdk/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 9c2bfbef4310c..a068a1507db57 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -80,7 +80,7 @@ "promptly": "^3.0.3", "proxy-agent": "^3.1.1", "semver": "^7.2.2", - "source-map-support": "^0.5.18", + "source-map-support": "^0.5.19", "table": "^5.4.6", "uuid": "^7.0.3", "yaml": "^1.9.2", diff --git a/yarn.lock b/yarn.lock index 56806635593fc..ee043bbbf1fb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11433,10 +11433,10 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.10, source-map-support@^0.5.18, source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.12: - version "0.5.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.18.tgz#f5f33489e270bd7f7d7e7b8debf283f3a4066960" - integrity sha512-9luZr/BZ2QeU6tO2uG8N2aZpVSli4TSAOAqFOyTO51AJcD9P99c0K1h6dD6r6qo5dyT44BR5exweOaLLeldTkQ== +source-map-support@^0.5.10, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.12: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" From 861ecdd9ad05171db51da6b6d45963bb59d1ead9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2020 17:48:50 +0000 Subject: [PATCH 11/12] chore(deps-dev): bump mock-fs from 4.11.0 to 4.12.0 (#7599) Bumps [mock-fs](https://github.com/tschaub/mock-fs) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/tschaub/mock-fs/releases) - [Changelog](https://github.com/tschaub/mock-fs/blob/master/changelog.md) - [Commits](https://github.com/tschaub/mock-fs/compare/v4.11.0...v4.12.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/@aws-cdk/cloud-assembly-schema/package.json | 2 +- packages/@aws-cdk/cx-api/package.json | 2 +- packages/cdk-assets/package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 58e803037afcd..bd35c48cc12dc 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -59,7 +59,7 @@ "@types/mock-fs": "^4.10.0", "cdk-build-tools": "0.0.0", "jest": "^25.4.0", - "mock-fs": "^4.11.0", + "mock-fs": "^4.12.0", "pkglint": "0.0.0", "typescript-json-schema": "^0.42.0" }, diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 277cf1c715c12..7819b2e73b322 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -75,7 +75,7 @@ "@types/semver": "^7.1.0", "cdk-build-tools": "0.0.0", "jest": "^25.4.0", - "mock-fs": "^4.11.0", + "mock-fs": "^4.12.0", "pkglint": "0.0.0" }, "repository": { diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index b4dca1c7f7f1c..9f93f08488516 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -38,7 +38,7 @@ "jszip": "^3.4.0", "cdk-build-tools": "0.0.0", "jest": "^25.4.0", - "mock-fs": "^4.11.0", + "mock-fs": "^4.12.0", "pkglint": "0.0.0" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index ee043bbbf1fb7..80fbef91ef9d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8776,10 +8776,10 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -mock-fs@^4.11.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.11.0.tgz#0828107e4b843a6ba855ecebfe3c6e073b69db92" - integrity sha512-Yp4o3/ZA15wsXqJTT+R+9w2AYIkD1i80Lds47wDbuUhOvQvm+O2EfjFZSz0pMgZZSPHRhGxgcd2+GL4+jZMtdw== +mock-fs@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.12.0.tgz#a5d50b12d2d75e5bec9dac3b67ffe3c41d31ade4" + integrity sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ== mockery@^2.1.0: version "2.1.0" From 1423c53fec4172ba21946ca6d33f63fc7a9d8337 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Fri, 24 Apr 2020 19:32:27 +0100 Subject: [PATCH 12/12] fix(apigateway): authorizer is not attached to RestApi across projects (#7596) When an Authorizer is defined in one node project, imported into another where it is wired up with a RestApi, synthesis throws the error 'Authorizer must be attached to a RestApi'. The root cause of this error is the `instanceof` check. If a user has their project set up in a way that more than one instance of the `@aws-cdk/aws-apigateway` module is present in their `node_modules/` folder, even if they are of the exact same version, type checking is bound to fail. Switching instead to a symbol property based comparison, so that type information survives across installations of the module. fixes #7377 --- .../@aws-cdk/aws-apigateway/lib/authorizer.ts | 17 ++++++++++++++- .../@aws-cdk/aws-apigateway/lib/method.ts | 2 +- .../aws-apigateway/test/test.authorizer.ts | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts index 3a2f3698a554d..a51f30e14514c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts @@ -1,14 +1,29 @@ -import { Resource } from '@aws-cdk/core'; +import { Construct, Resource, ResourceProps } from '@aws-cdk/core'; import { AuthorizationType } from './method'; import { RestApi } from './restapi'; +const AUTHORIZER_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.Authorizer'); + /** * Base class for all custom authorizers */ export abstract class Authorizer extends Resource implements IAuthorizer { + /** + * Return whether the given object is an Authorizer. + */ + public static isAuthorizer(x: any): x is Authorizer { + return x !== null && typeof(x) === 'object' && AUTHORIZER_SYMBOL in x; + } + public readonly abstract authorizerId: string; public readonly authorizationType?: AuthorizationType = AuthorizationType.CUSTOM; + public constructor(scope: Construct, id: string, props?: ResourceProps) { + super(scope, id, props); + + Object.defineProperty(this, AUTHORIZER_SYMBOL, { value: true }); + } + /** * Called when the authorizer is used from a specific REST API. * @internal diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index a459c4265a8d4..baafd1be53242 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -185,7 +185,7 @@ export class Method extends Resource { `which is different from what is required by the authorizer [${authorizer.authorizationType}]`); } - if (authorizer instanceof Authorizer) { + if (Authorizer.isAuthorizer(authorizer)) { authorizer._attachToApi(this.restApi); } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts b/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts new file mode 100644 index 0000000000000..ce5a5279228e3 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/test.authorizer.ts @@ -0,0 +1,21 @@ +import { Stack } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { Authorizer, RestApi } from '../lib'; + +export = { + 'isAuthorizer correctly detects an instance of type Authorizer'(test: Test) { + class MyAuthorizer extends Authorizer { + public readonly authorizerId = 'test-authorizer-id'; + public _attachToApi(_: RestApi): void { + // do nothing + } + } + const stack = new Stack(); + const authorizer = new MyAuthorizer(stack, 'authorizer'); + + test.ok(Authorizer.isAuthorizer(authorizer), 'type Authorizer expected but is not'); + test.ok(!Authorizer.isAuthorizer(stack), 'type Authorizer found, when not expected'); + + test.done(); + }, +}; \ No newline at end of file