From 66ae40cb5289d98b8df0fdb59d0b0194fd1193c8 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 19 Jan 2022 15:51:33 +0000 Subject: [PATCH 1/5] fix(cli): hotswap should wait for lambda's `updateFunctionCode` to complete There are [upcoming changes](https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/) that will rollout Lambda states to all Lambda Functions. Prior to this update (current functionality) when you made an `updateFunctionCode` request the function was immediately available for both invocation and future updates. Once this change is rolled out this will no longer be the case. With Lambda states, when you make an update to a Lambda Function, it will not be available for future updates until the `LastUpdateStatus` returns `Successful`. This PR introduces a custom waiter that will wait for the update to complete before proceeding. The waiter will wait until the `State=Active` and the `LastUpdateStatus=Successful`. The `State` controls whether or not the function can be invoked, and the `LastUpdateStatus` controls whether the function can be updated. Based on this, I am not considering a deployment complete until both are successful. To see a more in depth analysis of the different values see #18386. In my testing I found that the time it took for a function to go from `LastUpdateStatus=InProgress` to `LastUpdateStatus=Successful` was: - ~1 second for a zip Function not in a VPC - ~25 seconds for a container Function or a Function in a VPC - ~2 minutes to deploy a VPC function (best proxy for StateReasonCode=Restoring) There are a couple of built in waiters that could have been used for this, namely [functionUpdated](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#functionUpdated-waiter). This waiter uses `getFunctionConfiguration` which has a quota of 15 requests/second. In addition the waiter polls every 5 seconds and this cannot be configured. Because hotswapping is sensitive to any latency that is introduced, I created a custom waiter that uses `getFunction`. `getFunction` has a quota of 100 requests/second and the custom waiter can be configured to poll every 1 second or every 5 seconds depending on what type of function is being updated. fixes #18386 --- .../lib/api/hotswap/lambda-functions.ts | 69 +++++++++++++++---- .../api/hotswap/hotswap-deployments.test.ts | 22 +++++- .../test/api/hotswap/hotswap-test-setup.ts | 4 +- ...nctions-docker-hotswap-deployments.test.ts | 14 +++- ...mbda-functions-hotswap-deployments.test.ts | 14 +++- ...nctions-inline-hotswap-deployments.test.ts | 14 +++- ...rsions-aliases-hotswap-deployments.test.ts | 22 +++++- packages/aws-cdk/test/util/mock-sdk.ts | 4 +- 8 files changed, 141 insertions(+), 22 deletions(-) diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index d2966e756b69d..7b0df7b839f2b 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -1,5 +1,6 @@ import { Writable } from 'stream'; import * as archiver from 'archiver'; +import * as AWS from 'aws-sdk'; import { flatMap } from '../../util'; import { ISDK } from '../aws-auth'; import { CfnEvaluationException, EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template'; @@ -232,7 +233,7 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { const operations: Promise[] = []; if (resource.code !== undefined) { - const updateFunctionCodePromise = lambda.updateFunctionCode({ + const updateFunctionCodeResponse = await lambda.updateFunctionCode({ FunctionName: this.lambdaFunctionResource.physicalName, S3Bucket: resource.code.s3Bucket, S3Key: resource.code.s3Key, @@ -240,17 +241,9 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { ZipFile: resource.code.functionCodeZip, }).promise(); - // only if the code changed is there any point in publishing a new Version - if (this.lambdaFunctionResource.publishVersion) { - // we need to wait for the code update to be done before publishing a new Version - await updateFunctionCodePromise; - // if we don't wait for the Function to finish updating, - // we can get a "The operation cannot be performed at this time. An update is in progress for resource:" - // error when publishing a new Version - await lambda.waitFor('functionUpdated', { - FunctionName: this.lambdaFunctionResource.physicalName, - }).promise(); + await this.waitForLastUpdateStatusComplete(updateFunctionCodeResponse, lambda); + if (this.lambdaFunctionResource.publishVersion) { const publishVersionPromise = lambda.publishVersion({ FunctionName: this.lambdaFunctionResource.physicalName, }).promise(); @@ -269,8 +262,6 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { } else { operations.push(publishVersionPromise); } - } else { - operations.push(updateFunctionCodePromise); } } @@ -304,6 +295,58 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { // run all of our updates in parallel return Promise.all(operations); } + + /** + * After a Lambda Function is updated, it cannot be updated again until the + * `State=Active` and the `LastUpdateStatus=Successful`. + * + * Depending on the configuration of the Lambda Function this could happen relatively quickly + * or very slowly. For example, Zip based functions _not_ in a VPC can take ~1 second whereas VPC + * or Container functions can take ~25 seconds (and 'idle' VPC functions can take minutes). + */ + private async waitForLastUpdateStatusComplete(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { + let delay = 1; // in seconds + // if the function is deployed in a VPC or if it is a container image function + // then the update will take much longer and we can wait longer between checks + if (currentFunctionConfiguration.VpcConfig || currentFunctionConfiguration.PackageType === 'Image') { + delay = 5; + } + + // configure a custom waiter to wait for the function update to complete + (lambda as any).api.waiters.updateFunctionCodeToFinish = { + name: 'UpdateFunctionCodeToFinish', + operation: 'getFunction', + // equates to 1 minute for zip function not in a VPC and + // 5 minutes for container functions or function in a VPC + maxAttempts: 60, + delay, + acceptors: [ + { + state: 'success', + matcher: 'path', + argument: "(Configuration.LastUpdateStatus == 'Successful' && Configuration.State == 'Active')", + expected: true, + }, + { + state: 'failure', + matcher: 'path', + argument: 'Configuration.LastUpdateStatus', + expected: 'Failed', + }, + { + state: 'retry', + matcher: 'path', + argument: 'Configuration.LastUpdateStatus', + expected: 'InProgress', + }, + ], + }; + + const updateFunctionCodeWaiter = new (AWS as any).ResourceWaiter(lambda, 'updateFunctionCodeToFinish'); + await updateFunctionCodeWaiter.wait({ + FunctionName: this.lambdaFunctionResource.physicalName, + }).promise(); + } } /** diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 00a6b2095a569..252817e09bc51 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -2,17 +2,37 @@ import { Lambda, StepFunctions } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockGetFunction: (params: Lambda.Types.GetFunctionRequest) => Lambda.Types.GetFunctionResponse; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; let mockGetEndpointSuffix: () => string; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); + mockGetFunction = jest.fn().mockReturnValue({ + Configuration: { + State: 'Active', + LastUpdateStatus: 'Successful', + }, + }); mockUpdateMachineDefinition = jest.fn(); mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); hotswapMockSdkProvider.stubLambda({ + getFunction: mockGetFunction, updateFunctionCode: mockUpdateLambdaCode, + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, }); hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); hotswapMockSdkProvider.stubGetEndpointSuffix(mockGetEndpointSuffix); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index e47b127e68766..5bb6d45faefd1 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -83,8 +83,8 @@ export class HotswapMockSdkProvider { }); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - this.mockSdkProvider.stubLambda(stubs); + public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + this.mockSdkProvider.stubLambda(stubs, additionalProperties); } public setUpdateProjectMock(mockUpdateProject: (input: codebuild.UpdateProjectInput) => codebuild.UpdateProjectOutput) { diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 9aef8b8778cf0..8033a51067cf0 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -8,13 +8,25 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, }); }); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 0b43563f233d9..3ca2e1e3fad2d 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -8,13 +8,25 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, }); }); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts index 13554cc655dcb..4b0b7f70ff5e8 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts @@ -8,13 +8,25 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, }); }); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts index 4596bb2c96ac0..edd751f27e067 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts @@ -2,20 +2,40 @@ import { Lambda } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; +let mockGetFunction: (params: Lambda.Types.GetFunctionRequest) => Lambda.Types.GetFunctionResponse; let mockPublishVersion: jest.Mock; let mockUpdateAlias: (params: Lambda.UpdateAliasRequest) => Lambda.AliasConfiguration; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn(); + mockUpdateLambdaCode = jest.fn().mockReturnValue({}); + mockGetFunction = jest.fn().mockReturnValue({ + Configuration: { + State: 'Active', + LastUpdateStatus: 'Successful', + }, + }); mockPublishVersion = jest.fn(); mockUpdateAlias = jest.fn(); hotswapMockSdkProvider.stubLambda({ + getFunction: mockGetFunction, updateFunctionCode: mockUpdateLambdaCode, publishVersion: mockPublishVersion, updateAlias: mockUpdateAlias, waitFor: jest.fn(), + }, { + // these are needed for the waiter API that the ECS service hotswap uses + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, }); }); diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index a1ed61a431366..bb8eaae251c47 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -102,8 +102,8 @@ export class MockSdkProvider extends SdkProvider { (this.sdk as any).ssm = jest.fn().mockReturnValue(partialAwsService(stubs)); } - public stubLambda(stubs: SyncHandlerSubsetOf) { - (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs)); + public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + (this.sdk as any).lambda = jest.fn().mockReturnValue(partialAwsService(stubs, additionalProperties)); } public stubStepFunctions(stubs: SyncHandlerSubsetOf) { From 140a4e97009b25f6c6cbdcb5e2a8b7de1d272b97 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 19 Jan 2022 19:07:29 +0000 Subject: [PATCH 2/5] updating tests --- .../api/hotswap/hotswap-deployments.test.ts | 10 +-- ...nctions-docker-hotswap-deployments.test.ts | 71 ++++++++++++++++--- ...mbda-functions-hotswap-deployments.test.ts | 69 +++++++++++++++--- ...nctions-inline-hotswap-deployments.test.ts | 2 +- ...rsions-aliases-hotswap-deployments.test.ts | 10 +-- 5 files changed, 126 insertions(+), 36 deletions(-) diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 252817e09bc51..56100b79a9ee7 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -2,7 +2,6 @@ import { Lambda, StepFunctions } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; -let mockGetFunction: (params: Lambda.Types.GetFunctionRequest) => Lambda.Types.GetFunctionResponse; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockUpdateMachineDefinition: (params: StepFunctions.Types.UpdateStateMachineInput) => StepFunctions.Types.UpdateStateMachineOutput; let mockGetEndpointSuffix: () => string; @@ -10,19 +9,12 @@ let mockGetEndpointSuffix: () => string; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn().mockReturnValue({}); - mockGetFunction = jest.fn().mockReturnValue({ - Configuration: { - State: 'Active', - LastUpdateStatus: 'Successful', - }, - }); mockUpdateMachineDefinition = jest.fn(); mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); hotswapMockSdkProvider.stubLambda({ - getFunction: mockGetFunction, updateFunctionCode: mockUpdateLambdaCode, }, { - // these are needed for the waiter API that the ECS service hotswap uses + // these are needed for the waiter API that the Lambda service hotswap uses api: { waiters: {}, }, diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 8033a51067cf0..d13f57ec8b187 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -5,28 +5,30 @@ let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => La let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; +let mockMakeRequest: (operation: string, params: any) => {}; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); - mockUpdateLambdaCode = jest.fn().mockReturnValue({}); + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + PackageType: 'Image', + }); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, }, { - // these are needed for the waiter API that the ECS service hotswap uses + // these are needed for the waiter API that the Lambda service hotswap uses api: { waiters: {}, }, - makeRequest() { - return { - promise: () => Promise.resolve({}), - response: {}, - addListeners: () => {}, - }; - }, + makeRequest: mockMakeRequest, }); }); @@ -77,3 +79,54 @@ test('calls the updateLambdaCode() API when it receives only a code difference i ImageUri: 'new-image', }); }); + +test('calls the getFunction() API with a delay of 5', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'current-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + ImageUri: 'new-image', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + + // THEN + const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(waiters).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 3ca2e1e3fad2d..8200294c34d63 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -4,6 +4,7 @@ import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; +let mockMakeRequest: (operation: string, params: any) => {}; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { @@ -11,22 +12,21 @@ beforeEach(() => { mockUpdateLambdaCode = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); + mockMakeRequest = jest.fn().mockReturnValue({ + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, }, { - // these are needed for the waiter API that the ECS service hotswap uses + // these are needed for the waiter API that the Lambda service hotswap uses api: { waiters: {}, }, - makeRequest() { - return { - promise: () => Promise.resolve({}), - response: {}, - addListeners: () => {}, - }; - }, + makeRequest: mockMakeRequest, }); }); @@ -551,3 +551,56 @@ test('does not call the updateLambdaCode() API when a resource with type that is expect(deployStackResult).toBeUndefined(); expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); }); + +test('calls getFunction() after function code is updated', async () => { + // GIVEN + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(waiters).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts index 4b0b7f70ff5e8..623559b9e487b 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts @@ -16,7 +16,7 @@ beforeEach(() => { tagResource: mockTagResource, untagResource: mockUntagResource, }, { - // these are needed for the waiter API that the ECS service hotswap uses + // these are needed for the waiter API that the Lambda service hotswap uses api: { waiters: {}, }, diff --git a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts index edd751f27e067..0342412690f3b 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts @@ -2,7 +2,6 @@ import { Lambda } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; -let mockGetFunction: (params: Lambda.Types.GetFunctionRequest) => Lambda.Types.GetFunctionResponse; let mockPublishVersion: jest.Mock; let mockUpdateAlias: (params: Lambda.UpdateAliasRequest) => Lambda.AliasConfiguration; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; @@ -10,22 +9,15 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn().mockReturnValue({}); - mockGetFunction = jest.fn().mockReturnValue({ - Configuration: { - State: 'Active', - LastUpdateStatus: 'Successful', - }, - }); mockPublishVersion = jest.fn(); mockUpdateAlias = jest.fn(); hotswapMockSdkProvider.stubLambda({ - getFunction: mockGetFunction, updateFunctionCode: mockUpdateLambdaCode, publishVersion: mockPublishVersion, updateAlias: mockUpdateAlias, waitFor: jest.fn(), }, { - // these are needed for the waiter API that the ECS service hotswap uses + // these are needed for the waiter API that the Lambda service hotswap uses api: { waiters: {}, }, From 9f9e720c023af10091572788910f26fcb338a810 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:41:38 +0000 Subject: [PATCH 3/5] updating based on review --- .../lib/api/hotswap/lambda-functions.ts | 23 ++- ...mbda-functions-hotswap-deployments.test.ts | 140 +++++++++++++++++- 2 files changed, 148 insertions(+), 15 deletions(-) diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index 7b0df7b839f2b..7a211a9258ccc 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -305,12 +305,13 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { * or Container functions can take ~25 seconds (and 'idle' VPC functions can take minutes). */ private async waitForLastUpdateStatusComplete(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { - let delay = 1; // in seconds // if the function is deployed in a VPC or if it is a container image function // then the update will take much longer and we can wait longer between checks - if (currentFunctionConfiguration.VpcConfig || currentFunctionConfiguration.PackageType === 'Image') { - delay = 5; - } + // otherwise, the update will be quick, so a 1-second delay is fine + const delaySeconds = !(!currentFunctionConfiguration.VpcConfig?.VpcId || currentFunctionConfiguration.VpcConfig.VpcId === '') || + currentFunctionConfiguration.PackageType === 'Image' + ? 5 + : 1; // configure a custom waiter to wait for the function update to complete (lambda as any).api.waiters.updateFunctionCodeToFinish = { @@ -319,25 +320,19 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { // equates to 1 minute for zip function not in a VPC and // 5 minutes for container functions or function in a VPC maxAttempts: 60, - delay, + delay: delaySeconds, acceptors: [ { - state: 'success', matcher: 'path', - argument: "(Configuration.LastUpdateStatus == 'Successful' && Configuration.State == 'Active')", + argument: "Configuration.LastUpdateStatus == 'Successful' && Configuration.State == 'Active'", expected: true, + state: 'success', }, { - state: 'failure', matcher: 'path', argument: 'Configuration.LastUpdateStatus', expected: 'Failed', - }, - { - state: 'retry', - matcher: 'path', - argument: 'Configuration.LastUpdateStatus', - expected: 'InProgress', + state: 'failure', }, ], }; diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 8200294c34d63..a08e9c5a22949 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -552,7 +552,7 @@ test('does not call the updateLambdaCode() API when a resource with type that is expect(mockUpdateLambdaCode).not.toHaveBeenCalled(); }); -test('calls getFunction() after function code is updated', async () => { +test('calls getFunction() after function code is updated with delay 1', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { @@ -604,3 +604,141 @@ test('calls getFunction() after function code is updated', async () => { }), })); }); + +test('calls getFunction() after function code is updated and VpcId is empty string delay 1', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: '', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }, { + // these are needed for the waiter API that the Lambda service hotswap uses + api: { + waiters: {}, + }, + makeRequest: mockMakeRequest, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(waiters).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 1, + }), + })); +}); + +test('calls getFunction() after function code is updated on a VPC function with delay 5', async () => { + // GIVEN + mockUpdateLambdaCode = jest.fn().mockReturnValue({ + VpcConfig: { + VpcId: 'abc', + }, + }); + hotswapMockSdkProvider.stubLambda({ + updateFunctionCode: mockUpdateLambdaCode, + tagResource: mockTagResource, + untagResource: mockUntagResource, + }, { + // these are needed for the waiter API that the Lambda service hotswap uses + api: { + waiters: {}, + }, + makeRequest: mockMakeRequest, + }); + setup.setCurrentCfnStackTemplate({ + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'current-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'old-path', + }, + }, + }, + }); + const cdkStackArtifact = setup.cdkStackArtifactOf({ + template: { + Resources: { + Func: { + Type: 'AWS::Lambda::Function', + Properties: { + Code: { + S3Bucket: 'current-bucket', + S3Key: 'new-key', + }, + FunctionName: 'my-function', + }, + Metadata: { + 'aws:asset:path': 'new-path', + }, + }, + }, + }, + }); + + // WHEN + await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); + const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; + + // THEN + expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); + expect(waiters).toEqual(expect.objectContaining({ + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', + delay: 5, + }), + })); +}); From 4e6f78ef9658e8bfb34087dd12e42e39855a2d88 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 20 Jan 2022 20:16:10 +0000 Subject: [PATCH 4/5] updates based on review --- .../lib/api/hotswap/lambda-functions.ts | 2 +- .../test/api/hotswap/hotswap-test-setup.ts | 18 +++++++++++- ...nctions-docker-hotswap-deployments.test.ts | 7 +---- ...mbda-functions-hotswap-deployments.test.ts | 29 +++---------------- ...nctions-inline-hotswap-deployments.test.ts | 12 -------- ...rsions-aliases-hotswap-deployments.test.ts | 12 -------- 6 files changed, 23 insertions(+), 57 deletions(-) diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index 7a211a9258ccc..ca5555599258c 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -308,7 +308,7 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { // if the function is deployed in a VPC or if it is a container image function // then the update will take much longer and we can wait longer between checks // otherwise, the update will be quick, so a 1-second delay is fine - const delaySeconds = !(!currentFunctionConfiguration.VpcConfig?.VpcId || currentFunctionConfiguration.VpcConfig.VpcId === '') || + const delaySeconds = currentFunctionConfiguration.VpcConfig?.VpcId || currentFunctionConfiguration.PackageType === 'Image' ? 5 : 1; diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index 5bb6d45faefd1..68cdb14fcff8d 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -84,7 +84,23 @@ export class HotswapMockSdkProvider { } public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { - this.mockSdkProvider.stubLambda(stubs, additionalProperties); + this.mockSdkProvider.stubLambda(stubs, { + api: { + waiters: {}, + }, + makeRequest() { + return { + promise: () => Promise.resolve({}), + response: {}, + addListeners: () => {}, + }; + }, + ...additionalProperties, + }); + } + + public getLambdaApiWaiters() { + return (this.mockSdkProvider.sdk.lambda() as any).api.waiters; } public setUpdateProjectMock(mockUpdateProject: (input: codebuild.UpdateProjectInput) => codebuild.UpdateProjectOutput) { diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index d13f57ec8b187..400eec87c6bad 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -24,10 +24,6 @@ beforeEach(() => { tagResource: mockTagResource, untagResource: mockUntagResource, }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, makeRequest: mockMakeRequest, }); }); @@ -121,9 +117,8 @@ test('calls the getFunction() API with a delay of 5', async () => { await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); // THEN - const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); - expect(waiters).toEqual(expect.objectContaining({ + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ updateFunctionCodeToFinish: expect.objectContaining({ name: 'UpdateFunctionCodeToFinish', delay: 5, diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index a08e9c5a22949..4d446aad9559c 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -22,10 +22,6 @@ beforeEach(() => { tagResource: mockTagResource, untagResource: mockUntagResource, }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, makeRequest: mockMakeRequest, }); }); @@ -593,11 +589,10 @@ test('calls getFunction() after function code is updated with delay 1', async () // WHEN await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; // THEN expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); - expect(waiters).toEqual(expect.objectContaining({ + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ updateFunctionCodeToFinish: expect.objectContaining({ name: 'UpdateFunctionCodeToFinish', delay: 1, @@ -605,7 +600,7 @@ test('calls getFunction() after function code is updated with delay 1', async () })); }); -test('calls getFunction() after function code is updated and VpcId is empty string delay 1', async () => { +test('calls getFunction() after function code is updated and VpcId is empty string with delay 1', async () => { // GIVEN mockUpdateLambdaCode = jest.fn().mockReturnValue({ VpcConfig: { @@ -616,12 +611,6 @@ test('calls getFunction() after function code is updated and VpcId is empty stri updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, - }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, - makeRequest: mockMakeRequest, }); setup.setCurrentCfnStackTemplate({ Resources: { @@ -662,11 +651,9 @@ test('calls getFunction() after function code is updated and VpcId is empty stri // WHEN await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; // THEN - expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); - expect(waiters).toEqual(expect.objectContaining({ + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ updateFunctionCodeToFinish: expect.objectContaining({ name: 'UpdateFunctionCodeToFinish', delay: 1, @@ -685,12 +672,6 @@ test('calls getFunction() after function code is updated on a VPC function with updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, - }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, - makeRequest: mockMakeRequest, }); setup.setCurrentCfnStackTemplate({ Resources: { @@ -731,11 +712,9 @@ test('calls getFunction() after function code is updated on a VPC function with // WHEN await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - const waiters = (hotswapMockSdkProvider.mockSdkProvider.sdk.lambda() as any).api.waiters; // THEN - expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); - expect(waiters).toEqual(expect.objectContaining({ + expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ updateFunctionCodeToFinish: expect.objectContaining({ name: 'UpdateFunctionCodeToFinish', delay: 5, diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts index 623559b9e487b..478fd80b538bf 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-inline-hotswap-deployments.test.ts @@ -15,18 +15,6 @@ beforeEach(() => { updateFunctionCode: mockUpdateLambdaCode, tagResource: mockTagResource, untagResource: mockUntagResource, - }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, - makeRequest() { - return { - promise: () => Promise.resolve({}), - response: {}, - addListeners: () => {}, - }; - }, }); }); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts index 0342412690f3b..f937be7c5a5a0 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-versions-aliases-hotswap-deployments.test.ts @@ -16,18 +16,6 @@ beforeEach(() => { publishVersion: mockPublishVersion, updateAlias: mockUpdateAlias, waitFor: jest.fn(), - }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, - makeRequest() { - return { - promise: () => Promise.resolve({}), - response: {}, - addListeners: () => {}, - }; - }, }); }); From 757eb20ecdd6f452a60b50184b179a2b053d3621 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:25:30 +0000 Subject: [PATCH 5/5] updates --- .../aws-cdk/lib/api/hotswap/lambda-functions.ts | 13 +++++++------ .../test/api/hotswap/hotswap-deployments.test.ts | 12 ------------ .../aws-cdk/test/api/hotswap/hotswap-test-setup.ts | 9 +++++++-- ...bda-functions-docker-hotswap-deployments.test.ts | 2 +- .../lambda-functions-hotswap-deployments.test.ts | 2 +- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index ca5555599258c..ac87e25c29655 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -241,8 +241,9 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { ZipFile: resource.code.functionCodeZip, }).promise(); - await this.waitForLastUpdateStatusComplete(updateFunctionCodeResponse, lambda); + await this.waitForLambdasCodeUpdateToFinish(updateFunctionCodeResponse, lambda); + // only if the code changed is there any point in publishing a new Version if (this.lambdaFunctionResource.publishVersion) { const publishVersionPromise = lambda.publishVersion({ FunctionName: this.lambdaFunctionResource.physicalName, @@ -304,14 +305,14 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { * or very slowly. For example, Zip based functions _not_ in a VPC can take ~1 second whereas VPC * or Container functions can take ~25 seconds (and 'idle' VPC functions can take minutes). */ - private async waitForLastUpdateStatusComplete(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { + private async waitForLambdasCodeUpdateToFinish(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise { + const functionIsInVpcOrUsesDockerForCode = currentFunctionConfiguration.VpcConfig?.VpcId || + currentFunctionConfiguration.PackageType === 'Image'; + // if the function is deployed in a VPC or if it is a container image function // then the update will take much longer and we can wait longer between checks // otherwise, the update will be quick, so a 1-second delay is fine - const delaySeconds = currentFunctionConfiguration.VpcConfig?.VpcId || - currentFunctionConfiguration.PackageType === 'Image' - ? 5 - : 1; + const delaySeconds = functionIsInVpcOrUsesDockerForCode ? 5 : 1; // configure a custom waiter to wait for the function update to complete (lambda as any).api.waiters.updateFunctionCodeToFinish = { diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts index 56100b79a9ee7..67b0117a8d7ae 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-deployments.test.ts @@ -13,18 +13,6 @@ beforeEach(() => { mockGetEndpointSuffix = jest.fn(() => 'amazonaws.com'); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, - }, { - // these are needed for the waiter API that the Lambda service hotswap uses - api: { - waiters: {}, - }, - makeRequest() { - return { - promise: () => Promise.resolve({}), - response: {}, - addListeners: () => {}, - }; - }, }); hotswapMockSdkProvider.setUpdateStateMachineMock(mockUpdateMachineDefinition); hotswapMockSdkProvider.stubGetEndpointSuffix(mockGetEndpointSuffix); diff --git a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts index 68cdb14fcff8d..7b6aebb9a81ee 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -83,7 +83,11 @@ export class HotswapMockSdkProvider { }); } - public stubLambda(stubs: SyncHandlerSubsetOf, additionalProperties: { [key: string]: any } = {}) { + public stubLambda( + stubs: SyncHandlerSubsetOf, + serviceStubs?: SyncHandlerSubsetOf, + additionalProperties: { [key: string]: any } = {}, + ): void { this.mockSdkProvider.stubLambda(stubs, { api: { waiters: {}, @@ -95,11 +99,12 @@ export class HotswapMockSdkProvider { addListeners: () => {}, }; }, + ...serviceStubs, ...additionalProperties, }); } - public getLambdaApiWaiters() { + public getLambdaApiWaiters(): { [key: string]: any } { return (this.mockSdkProvider.sdk.lambda() as any).api.waiters; } diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 400eec87c6bad..3ed53e5fd908b 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts @@ -5,7 +5,7 @@ let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => La let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; -let mockMakeRequest: (operation: string, params: any) => {}; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); diff --git a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts index 4d446aad9559c..d96b8530bce88 100644 --- a/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts +++ b/packages/aws-cdk/test/api/hotswap/lambda-functions-hotswap-deployments.test.ts @@ -4,7 +4,7 @@ import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; -let mockMakeRequest: (operation: string, params: any) => {}; +let mockMakeRequest: (operation: string, params: any) => AWS.Request; let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => {