Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6061750
start
kaizencc Mar 14, 2022
ca89e1a
more progress
kaizencc Mar 14, 2022
6562425
add feature flag to lock lambda layer order
kaizencc Mar 16, 2022
5bb2c19
layer stuff
kaizencc Mar 17, 2022
c5af8f5
merge
kaizencc Apr 29, 2022
97f7f8c
revive branch and fix test
kaizencc Apr 29, 2022
144b38d
bake layer version into function version hash
kaizencc Apr 29, 2022
2bac2c9
add docstring
kaizencc Apr 29, 2022
5d45ae6
Merge branch 'master' into conroy/lambdalayer
kaizencc May 11, 2022
78fd23b
update test to better capture old behavior
kaizencc May 12, 2022
56db632
update integ tests
kaizencc May 13, 2022
a050989
Merge branch 'master' into conroy/lambdalayer
kaizencc May 24, 2022
68d06b3
Merge branch 'master' of https://github.com/aws/aws-cdk into conroy/l…
kaizencc May 24, 2022
3f62785
Merge branch 'master' into conroy/lambdalayer
Naumel May 25, 2022
4e6b561
Merge branch 'master' into conroy/lambdalayer
kaizencc May 26, 2022
0068a22
functionversionupgrade aspect
kaizencc May 26, 2022
8df2619
Merge branch 'master' of https://github.com/aws/aws-cdk into conroy/l…
kaizencc May 26, 2022
d850443
update integ tests and generalize functionversionupgrade
kaizencc May 26, 2022
404fcd3
add documentation
kaizencc May 26, 2022
594a590
Merge branch 'conroy/lambdalayer' of https://github.com/aws/aws-cdk i…
kaizencc May 26, 2022
dc4a0d2
fix for imported layers
kaizencc May 26, 2022
757ef2d
update triggers test
kaizencc May 26, 2022
a0a617b
update cloudfront snapshots
kaizencc May 26, 2022
5967433
update codedeploy snapshots
kaizencc May 27, 2022
3404aa3
Merge branch 'master' into conroy/lambdalayer
kaizencc May 27, 2022
47f7115
make layers internal api
kaizencc May 31, 2022
3be936f
readme updates
kaizencc May 31, 2022
72a3197
bake version arn into layer hash
kaizencc May 31, 2022
de03832
Merge branch 'master' into conroy/lambdalayer
mergify[bot] May 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"20.0.0"}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"LambdaServiceRoleA8ED4D3B"
]
},
"LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": {
"LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc": {
"Type": "AWS::Lambda::Version",
"Properties": {
"FunctionName": {
Expand All @@ -72,7 +72,7 @@
{
"EventType": "origin-request",
"LambdaFunctionARN": {
"Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d"
"Ref": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "18.0.0",
"version": "20.0.0",
"testCases": {
"aws-cloudfront/test/integ.distribution-lambda": {
"integ.distribution-lambda": {
"stacks": [
"integ-distribution-lambda"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "17.0.0",
"version": "20.0.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
Expand Down Expand Up @@ -30,14 +30,23 @@
"/integ-distribution-lambda/Lambda/CurrentVersion/Resource": [
{
"type": "aws:cdk:logicalId",
"data": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d"
"data": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc"
}
],
"/integ-distribution-lambda/Dist/Resource": [
{
"type": "aws:cdk:logicalId",
"data": "DistB3B78991"
}
],
"LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d": [
{
"type": "aws:cdk:logicalId",
"data": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d",
"trace": [
"!!DESTRUCTIVE_CHANGES: WILL_DESTROY"
]
}
]
},
"displayName": "integ-distribution-lambda"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"lambdaFunctionAssociations": [
{
"lambdaFunctionArn": {
"Ref": "LambdaCurrentVersionDF706F6A9a632a294ae3a9cd4d550f1c4e26619d"
"Ref": "LambdaCurrentVersionDF706F6A1ee13d0fa54e9f5621e8c7b616fc53fc"
},
"eventType": "origin-request"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"HandlerServiceRoleFCDC14AE"
]
},
"HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6": {
"HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51": {
"Type": "AWS::Lambda::Version",
"Properties": {
"FunctionName": {
Expand All @@ -101,7 +101,7 @@
},
"FunctionVersion": {
"Fn::GetAtt": [
"HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6",
"HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51",
"Version"
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"20.0.0"}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "18.0.0",
"version": "20.0.0",
"testCases": {
"aws-codedeploy/test/lambda/integ.deployment-group": {
"lambda/integ.deployment-group": {
"stacks": [
"aws-cdk-codedeploy-lambda"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "17.0.0",
"version": "20.0.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
Expand Down Expand Up @@ -68,7 +68,7 @@
"/aws-cdk-codedeploy-lambda/Handler/CurrentVersion/Resource": [
{
"type": "aws:cdk:logicalId",
"data": "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6"
"data": "HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51"
}
],
"/aws-cdk-codedeploy-lambda/AssetParameters/edb7466707eb899fbaee22c1e67f9443e9edcc2eeda0b58d8448f7c4157746b3/S3Bucket": [
Expand Down Expand Up @@ -202,6 +202,15 @@
"type": "aws:cdk:logicalId",
"data": "ServiceprincipalMap"
}
],
"HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6": [
{
"type": "aws:cdk:logicalId",
"data": "HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6",
"trace": [
"!!DESTRUCTIVE_CHANGES: WILL_DESTROY"
]
}
]
},
"displayName": "aws-cdk-codedeploy-lambda"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@
},
"functionVersion": {
"Fn::GetAtt": [
"HandlerCurrentVersion93FB80BFb2a9ce598bf2730613c07e406cddb6b6",
"HandlerCurrentVersion93FB80BFf2e6129c63154d1f37c0092df295ab51",
"Version"
]
},
Expand Down
35 changes: 33 additions & 2 deletions packages/@aws-cdk/aws-lambda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,15 @@ by default.
Existing users will need to enable the [feature flag]
`@aws-cdk/aws-lambda:recognizeVersionProps`. Since CloudFormation does not
allow duplicate versions, they will also need to make some modification to
their function so that a new version can be created. Any trivial change such as
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this readme

a whitespace change in the code or a no-op environment variable will suffice.
their function so that a new version can be created. To efficiently and trivially
modify all your lambda functions at once, users can attach the
`FunctionVersionUpgrade` aspect to the stack, which slightly modifes the
function description.

```ts
const stack = new Stack();
Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_VERSION_PROPS));
```

When the new logic is in effect, you may rarely come across the following error:
`The following properties are not recognized as version properties`. This will
Expand All @@ -304,6 +311,30 @@ record whether a new version should be generated when this property is changed.
This can be typically determined by checking whether the property can be
modified using the *[UpdateFunctionConfiguration]* API or not.

### `currentVersion`: Updated hashing logic for layer versions

An additional update to the hashing logic fixes two issues surrounding layers.
Prior to this change, updating the lambda layer version would have no affect on
the function version. Also, the order of lambda layers provided to the function
was unnecessarily baked into the hash.

This has been fixed in the AWS CDK but *existing* users need to opt-in via a
[feature flag]. Users who have run `cdk init` since this fix will be opted in,
by default.

Existing users will need to enable the [feature flag]
`@aws-cdk/aws-lambda:recognizeLayerVersion`. Since CloudFormation does not
allow duplicate versions, they will also need to make some modification to
their function so that a new version can be created. To efficiently and trivially
modify all your lambda functions at once, users can attach the
`FunctionVersionUpgrade` aspect to the stack, which slightly modifes the
function description.

```ts
const stack = new Stack();
Aspects.of(stack).add(new lambda.FunctionVersionUpgrade(LAMBDA_RECOGNIZE_LAYER_VERSION));
```

[feature flag]: https://docs.aws.amazon.com/cdk/latest/guide/featureflags.html
[property overrides]: https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_raw
[UpdateFunctionConfiguration]: https://docs.aws.amazon.com/lambda/latest/dg/API_UpdateFunctionConfiguration.html
Expand Down
33 changes: 32 additions & 1 deletion packages/@aws-cdk/aws-lambda/lib/function-hash.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as crypto from 'crypto';
import { CfnResource, FeatureFlags, Stack } from '@aws-cdk/core';
import { LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api';
import { LAMBDA_RECOGNIZE_LAYER_VERSION, LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api';
import { Function as LambdaFunction } from './function';
import { ILayerVersion } from './layers';

export function calculateFunctionHash(fn: LambdaFunction) {
const stack = Stack.of(fn);
Expand Down Expand Up @@ -29,6 +30,10 @@ export function calculateFunctionHash(fn: LambdaFunction) {
stringifiedConfig = JSON.stringify(config);
}

if (FeatureFlags.of(fn).isEnabled(LAMBDA_RECOGNIZE_LAYER_VERSION)) {
stringifiedConfig = stringifiedConfig + calculateLayersHash(fn.layers);
}

const hash = crypto.createHash('md5');
hash.update(stringifiedConfig);
return hash.digest('hex');
Expand Down Expand Up @@ -117,3 +122,29 @@ function sortProperties(properties: any) {
}
return ret;
}

function calculateLayersHash(layers: ILayerVersion[]): string {
const layerConfig: {[key: string]: any } = {};
for (const layer of layers) {
const stack = Stack.of(layer);
const layerResource = layer.node.defaultChild as CfnResource;
// if there is no layer resource, then the layer was imported
if (layerResource === undefined) {
continue;
}
const config = stack.resolve((layerResource as any)._toCloudFormation());
const resources = config.Resources;
const resourceKeys = Object.keys(resources);
if (resourceKeys.length !== 1) {
throw new Error(`Expected one rendered CloudFormation resource but found ${resourceKeys.length}`);
}
const logicalId = resourceKeys[0];
const properties = resources[logicalId].Properties;
// all properties require replacement, so they are all version locked.
layerConfig[layer.node.id] = properties;
}

const hash = crypto.createHash('md5');
hash.update(JSON.stringify(layerConfig));
return hash.digest('hex');
}
45 changes: 40 additions & 5 deletions packages/@aws-cdk/aws-lambda/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import * as kms from '@aws-cdk/aws-kms';
import * as logs from '@aws-cdk/aws-logs';
import * as sns from '@aws-cdk/aws-sns';
import * as sqs from '@aws-cdk/aws-sqs';
import { Annotations, ArnFormat, CfnResource, Duration, Fn, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core';
import { Annotations, ArnFormat, CfnResource, Duration, FeatureFlags, Fn, IAspect, IConstruct, Lazy, Names, Size, Stack, Token } from '@aws-cdk/core';
import { LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { AliasOptions, Alias } from './alias';
import { Architecture } from './architecture';
import { Code, CodeConfig } from './code';
import { ICodeSigningConfig } from './code-signing-config';
Expand All @@ -26,7 +28,6 @@ import { Runtime } from './runtime';
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line
import { LogRetentionRetryOptions } from './log-retention';
import { AliasOptions, Alias } from './alias';
import { addAlias } from './util';

/**
Expand Down Expand Up @@ -640,7 +641,10 @@ export class Function extends FunctionBase {

protected readonly canCreatePermissions = true;

private readonly layers: ILayerVersion[] = [];
/**
* The lambda layers that this function depends on.
*/
public readonly layers: ILayerVersion[] = [];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative to making this public is to send the layers directly into the calculateFunctionHash() like: calculateFunctionHash(fn, fn.layers). That seems awkward to me so I chose to make this public.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateFunctionHash is a private function, so I don't really care about its signature. I care more about making this public, which this change does... so I'd rather you didn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marked @internal instead. I need this to be publicly accessible for the aspect as well, and if my understanding of @internal is correct, this makes the most sense.


private _logGroup?: logs.ILogGroup;

Expand Down Expand Up @@ -777,7 +781,7 @@ export class Function extends FunctionBase {
zipFile: code.inlineCode,
imageUri: code.image?.imageUri,
},
layers: Lazy.list({ produce: () => this.layers.map(layer => layer.layerVersionArn) }, { omitEmpty: true }), // Evaluated on synthesis
layers: Lazy.list({ produce: () => this.renderLayers() }), // Evaluated on synthesis
handler: props.handler === Handler.FROM_IMAGE ? undefined : props.handler,
timeout: props.timeout && props.timeout.toSeconds(),
packageType: props.runtime === Runtime.FROM_IMAGE ? 'Image' : undefined,
Expand Down Expand Up @@ -920,7 +924,6 @@ export class Function extends FunctionBase {
// Currently no validations for compatible architectures since Lambda service
// allows layers configured with one architecture to be used with a Lambda function
// from another architecture.

this.layers.push(layer);
}
}
Expand Down Expand Up @@ -1050,6 +1053,18 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett
this.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy'));
}

private renderLayers() {
if (!this.layers || this.layers.length === 0) {
return undefined;
}

if (FeatureFlags.of(this).isEnabled(LAMBDA_RECOGNIZE_LAYER_VERSION)) {
this.layers.sort();
}

return this.layers.map(layer => layer.layerVersionArn);
}

private renderEnvironment() {
if (!this.environment || Object.keys(this.environment).length === 0) {
return undefined;
Expand Down Expand Up @@ -1269,3 +1284,23 @@ function undefinedIfNoKeys<A>(struct: A): A | undefined {
const allUndefined = Object.values(struct).every(val => val === undefined);
return allUndefined ? undefined : struct;
}

/**
* Aspect for upgrading function versions when the feature flag
* provided feature flag present. This can be necessary when the feature flag
* changes the function hash, as such changes must be associated with a new
* version. This aspect will change the function description in these cases,
* which "validates" the new function hash.
*/
export class FunctionVersionUpgrade implements IAspect {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this Aspect that should help users update their existing lambdas

constructor(private readonly featureFlag: string, private readonly enabled=true) {}

public visit(node: IConstruct): void {
if (node instanceof Function &&
this.enabled === FeatureFlags.of(node).isEnabled(this.featureFlag)) {
const cfnFunction = node.node.defaultChild as CfnFunction;
const desc = cfnFunction.description ? `${cfnFunction.description} ` : '';
cfnFunction.addPropertyOverride('Description', `${desc}version-hash:${calculateFunctionHash(node)}`);
}
};
}
4 changes: 1 addition & 3 deletions packages/@aws-cdk/aws-lambda/lib/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,7 @@ export class LayerVersion extends LayerVersionBase {
if (props.compatibleRuntimes && props.compatibleRuntimes.length === 0) {
throw new Error('Attempted to define a Lambda layer that supports no runtime!');
}
if (props.code.isInline) {
throw new Error('Lambda layers cannot be created from inline code');
}

// Allow usage of the code in this context...
const code = props.code.bind(this);
if (code.inlineCode) {
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-lambda/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Fixture with packages imported, but nothing else
import * as path from 'path';
import { Construct } from 'constructs';
import { CfnOutput, DockerImage, Duration, RemovalPolicy, Stack } from '@aws-cdk/core';
import { Aspects, CfnOutput, DockerImage, Duration, RemovalPolicy, Stack } from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as iam from '@aws-cdk/aws-iam';
import { LAMBDA_RECOGNIZE_VERSION_PROPS, LAMBDA_RECOGNIZE_LAYER_VERSION } from '@aws-cdk/cx-api';

class Fixture extends Stack {
constructor(scope: Construct, id: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@
"Arn"
]
},
"Description": "version-hash:7c384e097d7f7605c9405f788cdf9f7b",
"Handler": "index.handler",
"Runtime": "nodejs14.x"
},
"DependsOn": [
"MyLambdaServiceRole4539ECB6"
]
},
"MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822": {
"MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9": {
"Type": "AWS::Lambda::Version",
"Properties": {
"FunctionName": {
Expand All @@ -66,7 +67,7 @@
},
"FunctionVersion": {
"Fn::GetAtt": [
"MyLambdaCurrentVersionE7A382CCc9b5d5d60612e848a9b7c670d8802822",
"MyLambdaCurrentVersionE7A382CC0be4c7f7b82230afaf72d130ee920da9",
"Version"
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"17.0.0"}
{"version":"20.0.0"}
Loading