diff --git a/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource-provider/index.ts b/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource-provider/index.ts index 5dcf0d9c2e14f..fa9882991392a 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource-provider/index.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource-provider/index.ts @@ -33,12 +33,26 @@ function fixBooleans(object: object) { : v); } +/** + * Filters the keys of an object. + */ +function filterKeys(object: object, pred: (key: string) => boolean) { + return Object.entries(object) + .reduce( + (acc, [k, v]) => pred(k) + ? { ...acc, [k]: v } + : acc, + {} + ); +} + export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { try { console.log(JSON.stringify(event)); console.log('AWS SDK VERSION: ' + (AWS as any).VERSION); let physicalResourceId = (event as any).PhysicalResourceId; + let flatData: { [key: string]: string } = {}; let data: { [key: string]: string } = {}; const call: AwsSdkCall | undefined = event.ResourceProperties[event.RequestType]; @@ -47,18 +61,19 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent try { const response = await awsService[call.action](call.parameters && fixBooleans(call.parameters)).promise(); - data = flatten(response); + flatData = flatten(response); + data = call.outputPath + ? filterKeys(flatData, k => k.startsWith(call.outputPath!)) + : flatData; } catch (e) { if (!call.catchErrorPattern || !new RegExp(call.catchErrorPattern).test(e.code)) { throw e; } } - if (call.physicalResourceIdPath) { - physicalResourceId = data[call.physicalResourceIdPath]; - } else { - physicalResourceId = call.physicalResourceId!; - } + physicalResourceId = call.physicalResourceIdPath + ? flatData[call.physicalResourceIdPath] + : call.physicalResourceId; } await respond('SUCCESS', 'OK', physicalResourceId, data); diff --git a/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource.ts b/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource.ts index cf4d738db2fbf..6affcb325a3b4 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource.ts @@ -71,6 +71,18 @@ export interface AwsSdkCall { * @default use latest available API version */ readonly apiVersion?: string; + + /** + * Restrict the data returned by the custom resource to a specific path in + * the API response. Use this to limit the data returned by the custom + * resource if working with API calls that could potentially result in custom + * response objects exceeding the hard limit of 4096 bytes. + * + * Example for ECS / updateService: 'service.deploymentConfiguration.maximumPercent' + * + * @default return all data + */ + readonly outputPath?: string; } export interface AwsCustomResourceProps { @@ -158,6 +170,7 @@ export class AwsCustomResource extends cdk.Construct { /** * Returns response data for the AWS SDK call. + * * Example for S3 / listBucket : 'Buckets.0.Name' * * @param dataPath the path to the data diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.aws-custom-resource-provider.ts b/packages/@aws-cdk/aws-cloudformation/test/test.aws-custom-resource-provider.ts index a28f7035acad3..f1d659a65be75 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.aws-custom-resource-provider.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.aws-custom-resource-provider.ts @@ -223,5 +223,52 @@ export = { test.equal(request.isDone(), true); test.done(); - } + }, + + async 'restrict output path'(test: Test) { + const listObjectsFake = sinon.fake.resolves({ + Contents: [ + { + Key: 'first-key', + ETag: 'first-key-etag' + }, + { + Key: 'second-key', + ETag: 'second-key-etag', + } + ] + } as SDK.S3.ListObjectsOutput); + + AWS.mock('S3', 'listObjects', listObjectsFake); + + const event: AWSLambda.CloudFormationCustomResourceCreateEvent = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + Create: { + service: 'S3', + action: 'listObjects', + parameters: { + Bucket: 'my-bucket' + }, + physicalResourceId: 'id', + outputPath: 'Contents.0' + } as AwsSdkCall + } + }; + + const request = createRequest(body => + body.Status === 'SUCCESS' && + body.PhysicalResourceId === 'id' && + body.Data!['Contents.0.Key'] === 'first-key' && + body.Data!['Contents.1.Key'] === undefined + ); + + await handler(event, {} as AWSLambda.Context); + + test.equal(request.isDone(), true); + + test.done(); + }, };