Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudformation): add option to restrict data returned by AwsCustomResource #2859

Merged
merged 1 commit into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-cloudformation/lib/aws-custom-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
};