From be000a251b781b0b0870930992793df5a2fc4b01 Mon Sep 17 00:00:00 2001 From: Eli Polonsky Date: Wed, 27 Nov 2024 15:36:37 +0200 Subject: [PATCH] fix(cli): lambda hotswap fails if `lambda:GetFunctionConfiguration` action is not allowed (#32301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/aws/aws-cdk/issues/32219 ### Reason for this change In SDKv3, the standard `waitUntilFunctionUpdated` function invokes the `GetFunctionConfiguration` API, as opposed to SDKv2, which invoked `GetFunction`. This means that consumers of SDKv3 must allow the `lambda:GetFunctionConfiguration` action in their IAM role policy. ### Description of changes Use a different waiter function provided by the SDK, which invokes `GetFunction` instead of `GetFunctionConfiguration`, and thus restoring required IAM permissions to what they were in SDKv2. See https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-lambda/src/waiters/waitForFunctionUpdatedV2.ts#L10 > As opposed to https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-lambda/src/waiters/waitForFunctionUpdated.ts#L13 ### Description of how you validated changes Manul test. Assumed a role with the following policies: ![Screenshot 2024-11-27 at 9 34 25](https://github.com/user-attachments/assets/69415c37-6fe8-44d3-972c-1373ec55f46e) ```console ❯ cdk deploy --hotswap [09:29:11] ✨ Synthesis time: 2.72s ⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments ⚠️ They should only be used for development - never use them for your production Stacks! AwsCdkPlaygroundStack: deploying... [1/1] ✨ hotswapping resources: ✨ Lambda Function 'AwsCdkPlaygroundStack-Function76856677-7Rl7hiwwO5LQ' ❌ AwsCdkPlaygroundStack failed: TimeoutError: Resource is not in the expected state due to waiter status: TIMEOUT. Waiter has timed out. ``` Then, run the CLI from the PR. ```console ❯ /Users/epolon/dev/src/github.com/aws/aws-cdk/packages/aws-cdk/bin/cdk deploy --hotswap [10:03:00] ✨ Synthesis time: 3.46s ⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments ⚠️ They should only be used for development - never use them for your production Stacks! AwsCdkPlaygroundStack: deploying... [1/1] ✨ hotswapping resources: ✨ Lambda Function 'AwsCdkPlaygroundStack-Function76856677-7Rl7hiwwO5LQ' ✨ Lambda Function 'AwsCdkPlaygroundStack-Function76856677-7Rl7hiwwO5LQ' hotswapped! ✅ AwsCdkPlaygroundStack ✨ Deployment time: 12.72s Stack ARN: arn:aws:cloudformation:us-east-1:01234567890:stack/AwsCdkPlaygroundStack/22f2b380-a7cd-11ef-badd-0e08a8e0b5b1 ✨ Total time: 16.19s >>> elapsed time 23s ``` ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/aws-auth/sdk.ts | 4 ++-- .../aws-cdk/test/api/hotswap/hotswap-test-setup.ts | 8 +++++--- ...ambda-functions-docker-hotswap-deployments.test.ts | 6 +++--- .../lambda-functions-hotswap-deployments.test.ts | 11 +++++------ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index 9d28ed7958bd6..945b5f4513ad2 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -246,7 +246,7 @@ import { UpdateFunctionConfigurationCommand, type UpdateFunctionConfigurationCommandInput, type UpdateFunctionConfigurationCommandOutput, - waitUntilFunctionUpdated, + waitUntilFunctionUpdatedV2, } from '@aws-sdk/client-lambda'; import { GetHostedZoneCommand, @@ -841,7 +841,7 @@ export class SDK { delaySeconds: number, input: UpdateFunctionConfigurationCommandInput, ): Promise => { - return waitUntilFunctionUpdated( + return waitUntilFunctionUpdatedV2( { client, maxDelay: delaySeconds, 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 8a5020b290170..7fbc2a4ab92d0 100644 --- a/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/hotswap/hotswap-test-setup.ts @@ -1,6 +1,6 @@ import * as cxapi from '@aws-cdk/cx-api'; import { ListStackResourcesCommand, StackResourceSummary, StackStatus } from '@aws-sdk/client-cloudformation'; -import { GetFunctionConfigurationCommand } from '@aws-sdk/client-lambda'; +import { GetFunctionCommand } from '@aws-sdk/client-lambda'; import { ICloudFormationClient, SuccessfulDeployStackResult } from '../../../lib/api'; import { HotswapMode, HotswapPropertyOverrides } from '../../../lib/api/hotswap/common'; import * as deployments from '../../../lib/api/hotswap-deployments'; @@ -109,8 +109,10 @@ export class HotswapMockSdkProvider extends MockSdkProvider { constructor(rootStackName?: string) { super(); - mockLambdaClient.on(GetFunctionConfigurationCommand).resolves({ - LastUpdateStatus: 'Successful', + mockLambdaClient.on(GetFunctionCommand).resolves({ + Configuration: { + LastUpdateStatus: 'Successful', + }, }); mockCloudFormationClient.on(ListStackResourcesCommand).callsFake((input) => { 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 bf42795ae497f..023627a429a04 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 @@ -1,4 +1,4 @@ -import { UpdateFunctionCodeCommand, waitUntilFunctionUpdated } from '@aws-sdk/client-lambda'; +import { UpdateFunctionCodeCommand, waitUntilFunctionUpdatedV2 } from '@aws-sdk/client-lambda'; import * as setup from './hotswap-test-setup'; import { HotswapMode } from '../../../lib/api/hotswap/common'; import { mockLambdaClient } from '../../util/mock-sdk'; @@ -9,7 +9,7 @@ jest.mock('@aws-sdk/client-lambda', () => { return { ...original, - waitUntilFunctionUpdated: jest.fn(), + waitUntilFunctionUpdatedV2: jest.fn(), }; }); @@ -116,7 +116,7 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN - expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect(waitUntilFunctionUpdatedV2).toHaveBeenCalledWith( expect.objectContaining({ minDelay: 5, maxDelay: 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 a180e89d8d741..cf354a1aaa053 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 @@ -1,7 +1,7 @@ import { UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, - waitUntilFunctionUpdated, + waitUntilFunctionUpdatedV2, } from '@aws-sdk/client-lambda'; import * as setup from './hotswap-test-setup'; import { HotswapMode } from '../../../lib/api/hotswap/common'; @@ -10,10 +10,9 @@ import { silentTest } from '../../util/silent'; jest.mock('@aws-sdk/client-lambda', () => { const original = jest.requireActual('@aws-sdk/client-lambda'); - return { ...original, - waitUntilFunctionUpdated: jest.fn(), + waitUntilFunctionUpdatedV2: jest.fn(), }; }); @@ -617,7 +616,7 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot // THEN expect(mockLambdaClient).toHaveReceivedCommand(UpdateFunctionCodeCommand); - expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect(waitUntilFunctionUpdatedV2).toHaveBeenCalledWith( expect.objectContaining({ minDelay: 1, maxDelay: 1, @@ -675,7 +674,7 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN - expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect(waitUntilFunctionUpdatedV2).toHaveBeenCalledWith( expect.objectContaining({ minDelay: 1, maxDelay: 1, @@ -733,7 +732,7 @@ describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hot await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN - expect(waitUntilFunctionUpdated).toHaveBeenCalledWith( + expect(waitUntilFunctionUpdatedV2).toHaveBeenCalledWith( expect.objectContaining({ minDelay: 5, maxDelay: 5,