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 f9586e032ae5f..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); @@ -105,7 +105,9 @@ async function invokeUserFunction(functionArnE // automatically by the JavaScript SDK. const resp = await invokeFunction({ FunctionName: functionArn, - Payload: JSON.stringify(sanitizedPayload), + + // 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}"`), + })); + }); +}); + // ----------------------------------------------------------------------------------------------------------------------- /**