Skip to content

Commit

Permalink
Revert "feat(cli): support hotswapping Lambda function's description …
Browse files Browse the repository at this point in the history
…and environment variables" (aws#21509)

This reverts aws#21305 since it causes CLI integ test failures.
  • Loading branch information
vinayak-kukreja authored and josephedward committed Aug 30, 2022
1 parent 0263479 commit 586b8eb
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 287 deletions.
3 changes: 1 addition & 2 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
63 changes: 15 additions & 48 deletions packages/aws-cdk/lib/api/hotswap/lambda-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -250,32 +235,16 @@ class LambdaFunctionHotswapOperation implements HotswapOperation {
const resource = this.lambdaFunctionResource.resource;
const operations: Promise<any>[] = [];

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) {
Expand Down Expand Up @@ -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<void> {
private async waitForLambdasCodeUpdateToFinish(currentFunctionConfiguration: AWS.Lambda.FunctionConfiguration, lambda: AWS.Lambda): Promise<void> {
const functionIsInVpcOrUsesDockerForCode = currentFunctionConfiguration.VpcConfig?.VpcId ||
currentFunctionConfiguration.PackageType === 'Image';

Expand All @@ -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
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, AWS.AWSError>;
Expand All @@ -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({
Expand All @@ -23,7 +19,6 @@ beforeEach(() => {
});
hotswapMockSdkProvider.stubLambda({
updateFunctionCode: mockUpdateLambdaCode,
updateFunctionConfiguration: mockUpdateLambdaConfiguration,
tagResource: mockTagResource,
untagResource: mockUntagResource,
}, {
Expand Down Expand Up @@ -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,
}),
}));
Expand Down Expand Up @@ -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,
}),
}));
Expand Down Expand Up @@ -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',
});
});
Loading

0 comments on commit 586b8eb

Please sign in to comment.