diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts
index 97b7c4de61e4b..20c0144311b94 100644
--- a/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts
+++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts
@@ -29,7 +29,7 @@ async function onEvent(cfnRequest: AWSLambda.CloudFormationCustomResourceEvent)
cfnRequest.ResourceProperties = cfnRequest.ResourceProperties || { };
- const onEventResult = await invokeUserFunction(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, sanitizedRequest) as OnEventResponse;
+ const onEventResult = await invokeUserFunction(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, sanitizedRequest, cfnRequest.ResponseURL) as OnEventResponse;
log('onEvent returned:', onEventResult);
// merge the request and the result from onEvent to form the complete resource event
@@ -61,7 +61,7 @@ async function isComplete(event: AWSCDKAsyncCustomResource.IsCompleteRequest) {
const sanitizedRequest = { ...event, ResponseURL: '...' } as const;
log('isComplete', sanitizedRequest);
- const isCompleteResult = await invokeUserFunction(consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV, sanitizedRequest) as IsCompleteResponse;
+ const isCompleteResult = await invokeUserFunction(consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV, sanitizedRequest, event.ResponseURL) as IsCompleteResponse;
log('user isComplete returned:', isCompleteResult);
// if we are not complete, return false, and don't send a response back.
@@ -96,7 +96,7 @@ async function onTimeout(timeoutEvent: any) {
});
}
-async function invokeUserFunction(functionArnEnv: string, sanitizedPayload: A) {
+async function invokeUserFunction(functionArnEnv: string, sanitizedPayload: A, responseUrl: string) {
const functionArn = getEnv(functionArnEnv);
log(`executing user function ${functionArn} with payload`, sanitizedPayload);
@@ -106,8 +106,8 @@ async function invokeUserFunction(functionArnE
const resp = await invokeFunction({
FunctionName: functionArn,
- // Strip 'ResponseURL' -- the downstream CR doesn't need it and can only log it by accident
- Payload: JSON.stringify({ ...sanitizedPayload, ResponseURL: undefined }),
+ // Cannot strip 'ResponseURL' here as this would be a breaking change even though the downstream CR doesn't need it
+ Payload: JSON.stringify({ ...sanitizedPayload, ResponseURL: responseUrl }),
});
log('user function response:', resp, typeof(resp));
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 751e33e6408e0..2d35a9e63eac9 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
@@ -18,7 +18,10 @@ outbound.httpRequest = mocks.httpRequestMock;
outbound.invokeFunction = mocks.invokeFunctionMock;
outbound.startExecution = mocks.startExecutionMock;
+const invokeFunctionSpy = jest.spyOn(outbound, 'invokeFunction');
+
beforeEach(() => mocks.setup());
+afterEach(() => invokeFunctionSpy.mockClear());
test('async flow: isComplete returns true only after 3 times', async () => {
let isCompleteCalls = 0;
@@ -346,6 +349,41 @@ describe('if CREATE fails, the subsequent DELETE will be ignored', () => {
});
+describe('ResponseURL is passed to user function', () => {
+ test('for onEvent', async () => {
+ // GIVEN
+ mocks.onEventImplMock = async () => ({ PhysicalResourceId: MOCK_PHYSICAL_ID });
+
+ // WHEN
+ await simulateEvent({
+ RequestType: 'Create',
+ });
+
+ // THEN
+ expect(invokeFunctionSpy).toHaveBeenCalledTimes(1);
+ expect(invokeFunctionSpy).toBeCalledWith(expect.objectContaining({
+ Payload: expect.stringContaining(`"ResponseURL":"${mocks.MOCK_REQUEST.ResponseURL}"`),
+ }));
+ });
+
+ test('for isComplete', async () => {
+ // GIVEN
+ mocks.onEventImplMock = async () => ({ PhysicalResourceId: MOCK_PHYSICAL_ID });
+ mocks.isCompleteImplMock = async () => ({ IsComplete: true });
+
+ // WHEN
+ await simulateEvent({
+ RequestType: 'Create',
+ });
+
+ // THEN
+ expect(invokeFunctionSpy).toHaveBeenCalledTimes(2);
+ expect(invokeFunctionSpy).toHaveBeenLastCalledWith(expect.objectContaining({
+ Payload: expect.stringContaining(`"ResponseURL":"${mocks.MOCK_REQUEST.ResponseURL}"`),
+ }));
+ });
+});
+
// -----------------------------------------------------------------------------------------------------------------------
/**