Skip to content

Commit

Permalink
fix: lambda-nodejs default runtime regression
Browse files Browse the repository at this point in the history
Previously we changed the default version of the lambda-nodejs Function
construct to go from using the `builtInNodeJsCustomResourceRuntime`, a
map of regions to available versions, to `lambda.Runtime.NODEJS_18_X`.
The default `externalModule` configuration excluded the aws-sdk
version based on the runtime passed, excluding v2 for Node16 and under,
and v3 for Node18 and up, but users can pass their own bundling
configuration excluding `aws-sdk` while not explicitly passing a
runtime, which caused their functions to break.

Adds a new `lambda.Runtime` value for `NODEJS_LATEST`. This is central
reference for the latest version of NodeJS provided by the lamdba
service. It also includes a new property `isLatest` which can be used to
indicate that the runtime version may change over time. This can used to
indicate that relying on packages shipped with the environment may not
be relied upon if the version changes. We default to using the
`NODEJS_LATEST` runtime only if the feature flag is enabled. If the flag
is not enabled, use `NODEJS_16_X` to keep supporting users current
bundling configurations.

Additionally, add a warning to tell users if they are excluding a
package from their bundling that we know doesn't exist within the
runtime they are using. IE, if using `NODEJS_18_X` and the exclude list
includes `aws-sdk`, warn users that it won't be present.

fixes: #26732
  • Loading branch information
MrArnoldPalmer committed Aug 15, 2023
1 parent 716871f commit 9639ffd
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 42 deletions.
24 changes: 20 additions & 4 deletions packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as os from 'os';
import * as path from 'path';
import { IConstruct } from 'constructs';
import { PackageInstallation } from './package-installation';
import { LockFile, PackageManager } from './package-manager';
import { BundlingOptions, OutputFormat, SourceMapMode } from './types';
Expand Down Expand Up @@ -57,11 +58,11 @@ export class Bundling implements cdk.BundlingOptions {
/**
* esbuild bundled Lambda asset code
*/
public static bundle(options: BundlingProps): AssetCode {
public static bundle(scope: IConstruct, options: BundlingProps): AssetCode {
return Code.fromAsset(options.projectRoot, {
assetHash: options.assetHash,
assetHashType: options.assetHash ? cdk.AssetHashType.CUSTOM : cdk.AssetHashType.OUTPUT,
bundling: new Bundling(options),
bundling: new Bundling(scope, options),
});
}

Expand Down Expand Up @@ -97,7 +98,7 @@ export class Bundling implements cdk.BundlingOptions {
private readonly externals: string[];
private readonly packageManager: PackageManager;

constructor(private readonly props: BundlingProps) {
constructor(scope: IConstruct, private readonly props: BundlingProps) {
this.packageManager = PackageManager.fromLockFile(props.depsLockFilePath, props.logLevel);

Bundling.esbuildInstallation = Bundling.esbuildInstallation ?? PackageInstallation.detect('esbuild');
Expand All @@ -124,8 +125,23 @@ export class Bundling implements cdk.BundlingOptions {
throw new Error(`ECMAScript module output format is not supported by the ${props.runtime.name} runtime`);
}

// Modules to externalize when using a constant known version of the runtime.
// Mark aws-sdk as external by default (available in the runtime)
const isV2Runtime = isSdkV2Runtime(props.runtime);
const versionedExternals = isV2Runtime ? ['aws-sdk'] : ['@aws-sdk/*'];
// Don't automatically externalize any dependencies when using a `latest` runtime which may
// update versions in the future.
const defaultExternals = props.runtime?.isLatest ? [] : versionedExternals;
const externals = props.externalModules ?? defaultExternals;

if (isV2Runtime && externals.some((pkgName) => pkgName.startsWith('@aws-sdk/'))) {
cdk.Annotations.of(scope).addWarning('If you are relying on AWS SDK v3 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 18 or higher.');
} else if (externals.includes('aws-sdk')) {
cdk.Annotations.of(scope).addWarning('If you are relying on AWS SDK v2 to be present in the Lambda environment already, please explicitly configure a NodeJS runtime of Node 16 or lower.');
}

this.externals = [
...props.externalModules ?? (isSdkV2Runtime(props.runtime) ? ['aws-sdk'] : ['@aws-sdk/*']), // Mark aws-sdk as external by default (available in the runtime)
...externals,
...props.nodeModules ?? [], // Mark the modules that we are going to install as externals also
];

Expand Down
17 changes: 14 additions & 3 deletions packages/aws-cdk-lib/aws-lambda-nodejs/lib/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { BundlingOptions } from './types';
import { callsites, findUpMultiple } from './util';
import { Architecture } from '../../aws-lambda';
import * as lambda from '../../aws-lambda';
import { FeatureFlags } from '../../core';
import { LAMBDA_NODEJS_USE_LATEST_RUNTIME } from '../../cx-api';

/**
* Properties for a NodejsFunction
Expand Down Expand Up @@ -98,14 +100,15 @@ export class NodejsFunction extends lambda.Function {
const architecture = props.architecture ?? Architecture.X86_64;
const depsLockFilePath = findLockFile(props.depsLockFilePath);
const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath);
const runtime = getRuntime(scope, props);

super(scope, id, {
...props,
runtime: props.runtime ?? lambda.Runtime.NODEJS_18_X,
code: Bundling.bundle({
runtime,
code: Bundling.bundle(scope, {
...props.bundling ?? {},
entry,
runtime: props.runtime,
runtime,
architecture,
depsLockFilePath,
projectRoot,
Expand All @@ -118,6 +121,14 @@ export class NodejsFunction extends lambda.Function {
this.addEnvironment('AWS_NODEJS_CONNECTION_REUSE_ENABLED', '1', { removeInEdge: true });
}
}

}

function getRuntime(scope: Construct, props: NodejsFunctionProps): lambda.Runtime {
const defaultRuntime = FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_USE_LATEST_RUNTIME)
? lambda.Runtime.NODEJS_LATEST
: lambda.Runtime.NODEJS_16_X;
return props.runtime ?? defaultRuntime;
}

/**
Expand Down
Loading

0 comments on commit 9639ffd

Please sign in to comment.