diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index a9a35f644d2fa..4fe06d1878abf 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -373,8 +373,7 @@ and that you have the necessary IAM permissions to update the resources that are Hotswapping is currently supported for the following changes (additional changes will be supported in the future): -- Code asset (including Docker image and inline code), tag changes, and configuration changes (only - description and environment variables are supported) of AWS Lambda functions. +- Code asset (including Docker image and inline code) and tag changes of AWS Lambda functions. - AWS Lambda Versions and Aliases changes. - Definition changes of AWS Step Functions State Machines. - Container asset changes of AWS ECS Services. diff --git a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts index 43503c84c2b43..adcc8b5c1aea8 100644 --- a/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts +++ b/packages/aws-cdk/lib/api/hotswap/lambda-functions.ts @@ -107,8 +107,6 @@ async function isLambdaFunctionCodeOnlyChange( const propertyUpdates = change.propertyUpdates; let code: LambdaFunctionCode | undefined = undefined; let tags: LambdaFunctionTags | undefined = undefined; - let description: string | undefined = undefined; - let environment: { [key: string]: string } | undefined = undefined; for (const updatedPropName in propertyUpdates) { const updatedProp = propertyUpdates[updatedPropName]; @@ -177,19 +175,12 @@ async function isLambdaFunctionCodeOnlyChange( tags = { tagUpdates }; } break; - case 'Description': - description = updatedProp.newValue; - break; - case 'Environment': - environment = updatedProp.newValue; - break; default: return ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT; } } - const configurations = description || environment ? { description, environment } : undefined; - return code || tags || configurations ? { code, tags, configurations } : ChangeHotswapImpact.IRRELEVANT; + return code || tags ? { code, tags } : ChangeHotswapImpact.IRRELEVANT; } interface CfnDiffTagValue { @@ -212,15 +203,9 @@ interface LambdaFunctionTags { readonly tagUpdates: { [tag : string] : string | TagDeletion }; } -interface LambdaFunctionConfigurations { - readonly description?: string; - readonly environment?: { [key: string]: string }; -} - interface LambdaFunctionChange { readonly code?: LambdaFunctionCode; readonly tags?: LambdaFunctionTags; - readonly configurations?: LambdaFunctionConfigurations; } interface LambdaFunctionResource { @@ -250,32 +235,16 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { const resource = this.lambdaFunctionResource.resource; const operations: Promise[] = []; - if (resource.code !== undefined || resource.configurations !== undefined) { - if (resource.code !== undefined) { - const updateFunctionCodeResponse = await lambda.updateFunctionCode({ - FunctionName: this.lambdaFunctionResource.physicalName, - S3Bucket: resource.code.s3Bucket, - S3Key: resource.code.s3Key, - ImageUri: resource.code.imageUri, - ZipFile: resource.code.functionCodeZip, - }).promise(); - - await this.waitForLambdasPropertiesUpdateToFinish(updateFunctionCodeResponse, lambda); - } + if (resource.code !== undefined) { + const updateFunctionCodeResponse = await lambda.updateFunctionCode({ + FunctionName: this.lambdaFunctionResource.physicalName, + S3Bucket: resource.code.s3Bucket, + S3Key: resource.code.s3Key, + ImageUri: resource.code.imageUri, + ZipFile: resource.code.functionCodeZip, + }).promise(); - if (resource.configurations !== undefined) { - const updateRequest: AWS.Lambda.UpdateFunctionConfigurationRequest = { - FunctionName: this.lambdaFunctionResource.physicalName, - }; - if (resource.configurations.description !== undefined) { - updateRequest.Description = resource.configurations.description; - } - if (resource.configurations.environment !== undefined) { - updateRequest.Environment = resource.configurations.environment; - } - const updateFunctionCodeResponse = await lambda.updateFunctionConfiguration(updateRequest).promise(); - await this.waitForLambdasPropertiesUpdateToFinish(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) { @@ -339,9 +308,7 @@ 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 waitForLambdasPropertiesUpdateToFinish( - 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'; @@ -351,8 +318,8 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { const delaySeconds = functionIsInVpcOrUsesDockerForCode ? 5 : 1; // configure a custom waiter to wait for the function update to complete - (lambda as any).api.waiters.updateFunctionPropertiesToFinish = { - name: 'UpdateFunctionPropertiesToFinish', + (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 @@ -374,8 +341,8 @@ class LambdaFunctionHotswapOperation implements HotswapOperation { ], }; - const updateFunctionPropertiesWaiter = new (AWS as any).ResourceWaiter(lambda, 'updateFunctionPropertiesToFinish'); - await updateFunctionPropertiesWaiter.wait({ + 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/lambda-functions-docker-hotswap-deployments.test.ts b/packages/aws-cdk/test/api/hotswap/lambda-functions-docker-hotswap-deployments.test.ts index 9685b3ef1c0b4..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 @@ -119,8 +119,8 @@ test('calls the getFunction() API with a delay of 5', async () => { // THEN expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ - updateFunctionPropertiesToFinish: expect.objectContaining({ - name: 'UpdateFunctionPropertiesToFinish', + 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 8a1d356a1e9ca..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 @@ -2,9 +2,6 @@ import { Lambda } from 'aws-sdk'; import * as setup from './hotswap-test-setup'; let mockUpdateLambdaCode: (params: Lambda.Types.UpdateFunctionCodeRequest) => Lambda.Types.FunctionConfiguration; -let mockUpdateLambdaConfiguration: ( - params: Lambda.Types.UpdateFunctionConfigurationRequest -) => Lambda.Types.FunctionConfiguration; let mockTagResource: (params: Lambda.Types.TagResourceRequest) => {}; let mockUntagResource: (params: Lambda.Types.UntagResourceRequest) => {}; let mockMakeRequest: (operation: string, params: any) => AWS.Request; @@ -13,7 +10,6 @@ let hotswapMockSdkProvider: setup.HotswapMockSdkProvider; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); mockUpdateLambdaCode = jest.fn().mockReturnValue({}); - mockUpdateLambdaConfiguration = jest.fn().mockReturnValue({}); mockTagResource = jest.fn(); mockUntagResource = jest.fn(); mockMakeRequest = jest.fn().mockReturnValue({ @@ -23,7 +19,6 @@ beforeEach(() => { }); hotswapMockSdkProvider.stubLambda({ updateFunctionCode: mockUpdateLambdaCode, - updateFunctionConfiguration: mockUpdateLambdaConfiguration, tagResource: mockTagResource, untagResource: mockUntagResource, }, { @@ -598,8 +593,8 @@ test('calls getFunction() after function code is updated with delay 1', async () // THEN expect(mockMakeRequest).toHaveBeenCalledWith('getFunction', { FunctionName: 'my-function' }); expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ - updateFunctionPropertiesToFinish: expect.objectContaining({ - name: 'UpdateFunctionPropertiesToFinish', + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', delay: 1, }), })); @@ -659,8 +654,8 @@ test('calls getFunction() after function code is updated and VpcId is empty stri // THEN expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ - updateFunctionPropertiesToFinish: expect.objectContaining({ - name: 'UpdateFunctionPropertiesToFinish', + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', delay: 1, }), })); @@ -720,188 +715,9 @@ test('calls getFunction() after function code is updated on a VPC function with // THEN expect(hotswapMockSdkProvider.getLambdaApiWaiters()).toEqual(expect.objectContaining({ - updateFunctionPropertiesToFinish: expect.objectContaining({ - name: 'UpdateFunctionPropertiesToFinish', + updateFunctionCodeToFinish: expect.objectContaining({ + name: 'UpdateFunctionCodeToFinish', delay: 5, }), })); }); - - -test('calls the updateLambdaConfiguration() API when it receives difference in Description field of a Lambda function', async () => { - // GIVEN - setup.setCurrentCfnStackTemplate({ - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 's3-bucket', - S3Key: 's3-key', - }, - FunctionName: 'my-function', - Description: 'Old Description', - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }); - const cdkStackArtifact = setup.cdkStackArtifactOf({ - template: { - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 's3-bucket', - S3Key: 's3-key', - }, - FunctionName: 'my-function', - Description: 'New Description', - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }, - }); - - // WHEN - const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - - // THEN - expect(deployStackResult).not.toBeUndefined(); - expect(mockUpdateLambdaConfiguration).toHaveBeenCalledWith({ - FunctionName: 'my-function', - Description: 'New Description', - }); -}); - -test('calls the updateLambdaConfiguration() API when it receives difference in Environment field of a Lambda function', async () => { - // GIVEN - setup.setCurrentCfnStackTemplate({ - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 's3-bucket', - S3Key: 's3-key', - }, - FunctionName: 'my-function', - Environment: { - Variables: { - Key1: 'Value1', - Key2: 'Value2', - }, - }, - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }); - const cdkStackArtifact = setup.cdkStackArtifactOf({ - template: { - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 's3-bucket', - S3Key: 's3-key', - }, - FunctionName: 'my-function', - Environment: { - Variables: { - Key1: 'Value1', - Key2: 'Value2', - NewKey: 'NewValue', - }, - }, - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }, - }); - - // WHEN - const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - - // THEN - expect(deployStackResult).not.toBeUndefined(); - expect(mockUpdateLambdaConfiguration).toHaveBeenCalledWith({ - FunctionName: 'my-function', - Environment: { - Variables: { - Key1: 'Value1', - Key2: 'Value2', - NewKey: 'NewValue', - }, - }, - }); -}); - -test('calls both updateLambdaCode() and updateLambdaConfiguration() API when it receives both code and configuration change', async () => { - // GIVEN - setup.setCurrentCfnStackTemplate({ - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 'current-bucket', - S3Key: 'current-key', - }, - FunctionName: 'my-function', - Description: 'Old Description', - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }); - const cdkStackArtifact = setup.cdkStackArtifactOf({ - template: { - Resources: { - Func: { - Type: 'AWS::Lambda::Function', - Properties: { - Code: { - S3Bucket: 'new-bucket', - S3Key: 'new-key', - }, - FunctionName: 'my-function', - Description: 'New Description', - }, - Metadata: { - 'aws:asset:path': 'asset-path', - }, - }, - }, - }, - }); - - // WHEN - const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(cdkStackArtifact); - - // THEN - expect(deployStackResult).not.toBeUndefined(); - expect(mockUpdateLambdaConfiguration).toHaveBeenCalledWith({ - FunctionName: 'my-function', - Description: 'New Description', - }); - expect(mockUpdateLambdaCode).toHaveBeenCalledWith({ - FunctionName: 'my-function', - S3Bucket: 'new-bucket', - S3Key: 'new-key', - }); -}); diff --git a/packages/aws-cdk/test/integ/cli/app/app.js b/packages/aws-cdk/test/integ/cli/app/app.js index 9f5fe09ca68c7..5db2d80fe42df 100755 --- a/packages/aws-cdk/test/integ/cli/app/app.js +++ b/packages/aws-cdk/test/integ/cli/app/app.js @@ -228,24 +228,6 @@ class LambdaStack extends cdk.Stack { } } -class LambdaHotswapStack extends cdk.Stack { - constructor(parent, id, props) { - super(parent, id, props); - - const fn = new lambda.Function(this, 'my-function', { - code: lambda.Code.asset(path.join(__dirname, 'lambda')), - runtime: lambda.Runtime.NODEJS_14_X, - handler: 'index.handler', - description: new Date().toISOString(), - environment: { - SomeVariable: new Date().toISOString(), - } - }); - - new cdk.CfnOutput(this, 'FunctionName', { value: fn.functionName }); - } -} - class DockerStack extends cdk.Stack { constructor(parent, id, props) { super(parent, id, props); @@ -397,7 +379,6 @@ switch (stackSet) { new MissingSSMParameterStack(app, `${stackPrefix}-missing-ssm-parameter`, { env: defaultEnv }); new LambdaStack(app, `${stackPrefix}-lambda`); - new LambdaHotswapStack(app, `${stackPrefix}-lambda-hotswap`); new DockerStack(app, `${stackPrefix}-docker`); new DockerStackWithCustomFile(app, `${stackPrefix}-docker-with-custom-file`); new FailedStack(app, `${stackPrefix}-failed`) diff --git a/packages/aws-cdk/test/integ/cli/cli.integtest.ts b/packages/aws-cdk/test/integ/cli/cli.integtest.ts index c937c4e83d83c..b3ff437771601 100644 --- a/packages/aws-cdk/test/integ/cli/cli.integtest.ts +++ b/packages/aws-cdk/test/integ/cli/cli.integtest.ts @@ -1096,32 +1096,6 @@ integTest('test resource import', withDefaultFixture(async (fixture) => { } })); -integTest('hotswap deployment supports Lambda function\'s description and environment variables', withDefaultFixture(async (fixture) => { - // GIVEN - const stackArn = await fixture.cdkDeploy('lambda-hotswap', { - captureStderr: false, - }); - - // WHEN - const deployOutput = await fixture.cdkDeploy('lambda-hotswap', { - options: ['--hotswap'], - captureStderr: true, - onlyStderr: true, - }); - - const response = await fixture.aws.cloudFormation('describeStacks', { - StackName: stackArn, - }); - const functionName = response.Stacks?.[0].Outputs?.[0].OutputValue; - - // THEN - - // The deployment should not trigger a full deployment, thus the stack's status must remains - // "CREATE_COMPLETE" - expect(response.Stacks?.[0].StackStatus).toEqual('CREATE_COMPLETE'); - expect(deployOutput).toContain(`Lambda Function '${functionName}' hotswapped!`); -})); - async function listChildren(parent: string, pred: (x: string) => Promise) { const ret = new Array(); for (const child of await fs.readdir(parent, { encoding: 'utf-8' })) {