in packages/aws-cdk-lib/aws-lambda/lib/function.ts [914:1174]
constructor(scope: Construct, id: string, props: FunctionProps) {
super(scope, id, {
physicalName: props.functionName,
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
if (props.functionName && !Token.isUnresolved(props.functionName)) {
if (props.functionName.length > 64) {
throw new ValidationError(`Function name can not be longer than 64 characters but has ${props.functionName.length} characters.`, this);
}
if (!/^[a-zA-Z0-9-_]+$/.test(props.functionName)) {
throw new ValidationError(`Function name ${props.functionName} can contain only letters, numbers, hyphens, or underscores with no spaces.`, this);
}
}
if (props.description && !Token.isUnresolved(props.description)) {
if (props.description.length > 256) {
throw new ValidationError(`Function description can not be longer than 256 characters but has ${props.description.length} characters.`, this);
}
}
const managedPolicies = new Array<iam.IManagedPolicy>();
// the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
managedPolicies.push(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'));
if (props.vpc) {
// Policy that will have ENI creation permissions
managedPolicies.push(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
}
this.role = props.role || new iam.Role(this, 'ServiceRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies,
});
this.grantPrincipal = this.role;
// add additional managed policies when necessary
if (props.filesystem) {
const config = props.filesystem.config;
if (!Token.isUnresolved(config.localMountPath)) {
if (!/^\/mnt\/[a-zA-Z0-9-_.]+$/.test(config.localMountPath)) {
throw new ValidationError(`Local mount path should match with ^/mnt/[a-zA-Z0-9-_.]+$ but given ${config.localMountPath}.`, this);
}
if (config.localMountPath.length > 160) {
throw new ValidationError(`Local mount path can not be longer than 160 characters but has ${config.localMountPath.length} characters.`, this);
}
}
if (config.policies) {
config.policies.forEach(p => {
this.role?.addToPrincipalPolicy(p);
});
}
}
for (const statement of (props.initialPolicy || [])) {
this.role.addToPrincipalPolicy(statement);
}
const code = props.code.bind(this);
verifyCodeConfig(code, props);
let profilingGroupEnvironmentVariables: { [key: string]: string } = {};
if (props.profilingGroup && props.profiling !== false) {
this.validateProfiling(props);
props.profilingGroup.grantPublish(this.role);
profilingGroupEnvironmentVariables = {
AWS_CODEGURU_PROFILER_GROUP_NAME: props.profilingGroup.profilingGroupName,
AWS_CODEGURU_PROFILER_TARGET_REGION: props.profilingGroup.env.region,
AWS_CODEGURU_PROFILER_GROUP_ARN: props.profilingGroup.profilingGroupArn,
AWS_CODEGURU_PROFILER_ENABLED: 'TRUE',
};
} else if (props.profiling) {
this.validateProfiling(props);
const profilingGroup = new ProfilingGroup(this, 'ProfilingGroup', {
computePlatform: ComputePlatform.AWS_LAMBDA,
});
profilingGroup.grantPublish(this.role);
profilingGroupEnvironmentVariables = {
AWS_CODEGURU_PROFILER_GROUP_NAME: profilingGroup.profilingGroupName,
AWS_CODEGURU_PROFILER_TARGET_REGION: profilingGroup.env.region,
AWS_CODEGURU_PROFILER_GROUP_ARN: profilingGroup.profilingGroupArn,
AWS_CODEGURU_PROFILER_ENABLED: 'TRUE',
};
}
const env = { ...profilingGroupEnvironmentVariables, ...props.environment };
for (const [key, value] of Object.entries(env)) {
this.addEnvironment(key, value);
}
// DLQ can be either sns.ITopic or sqs.IQueue
const dlqTopicOrQueue = this.buildDeadLetterQueue(props);
if (dlqTopicOrQueue !== undefined) {
if (this.isQueue(dlqTopicOrQueue)) {
this.deadLetterQueue = dlqTopicOrQueue;
} else {
this.deadLetterTopic = dlqTopicOrQueue;
}
}
let fileSystemConfigs: CfnFunction.FileSystemConfigProperty[] | undefined = undefined;
if (props.filesystem) {
fileSystemConfigs = [{
arn: props.filesystem.config.arn,
localMountPath: props.filesystem.config.localMountPath,
}];
}
if (props.architecture && props.architectures !== undefined) {
throw new ValidationError('Either architecture or architectures must be specified but not both.', this);
}
if (props.architectures && props.architectures.length > 1) {
throw new ValidationError('Only one architecture must be specified.', this);
}
this._architecture = props.architecture ?? (props.architectures && props.architectures[0]);
if (props.ephemeralStorageSize && !props.ephemeralStorageSize.isUnresolved()
&& (props.ephemeralStorageSize.toMebibytes() < 512 || props.ephemeralStorageSize.toMebibytes() > 10240)) {
throw new ValidationError(`Ephemeral storage size must be between 512 and 10240 MB, received ${props.ephemeralStorageSize}.`, this);
}
const resource: CfnFunction = new CfnFunction(this, 'Resource', {
functionName: this.physicalName,
description: props.description,
code: {
s3Bucket: code.s3Location && code.s3Location.bucketName,
s3Key: code.s3Location && code.s3Location.objectKey,
s3ObjectVersion: code.s3Location && code.s3Location.objectVersion,
zipFile: code.inlineCode,
imageUri: code.image?.imageUri,
sourceKmsKeyArn: code.sourceKMSKeyArn,
},
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,
runtime: props.runtime === Runtime.FROM_IMAGE ? undefined : props.runtime.name,
role: this.role.roleArn,
// Uncached because calling '_checkEdgeCompatibility', which gets called in the resolve of another
// Token, actually *modifies* the 'environment' map.
environment: Lazy.uncachedAny({ produce: () => this.renderEnvironment() }),
memorySize: props.memorySize,
ephemeralStorage: props.ephemeralStorageSize ? {
size: props.ephemeralStorageSize.toMebibytes(),
} : undefined,
vpcConfig: this.configureVpc(props),
deadLetterConfig: this.buildDeadLetterConfig(dlqTopicOrQueue),
reservedConcurrentExecutions: props.reservedConcurrentExecutions,
imageConfig: undefinedIfNoKeys({
command: code.image?.cmd,
entryPoint: code.image?.entrypoint,
workingDirectory: code.image?.workingDirectory,
}),
kmsKeyArn: props.environmentEncryption?.keyArn,
fileSystemConfigs,
codeSigningConfigArn: props.codeSigningConfig?.codeSigningConfigArn,
architectures: this._architecture ? [this._architecture.name] : undefined,
runtimeManagementConfig: props.runtimeManagementMode?.runtimeManagementConfig,
snapStart: this.configureSnapStart(props),
loggingConfig: this.getLoggingConfig(props),
recursiveLoop: props.recursiveLoop,
});
if ((props.tracing !== undefined) || (props.adotInstrumentation !== undefined)) {
resource.tracingConfig = this.buildTracingConfig(props.tracing ?? Tracing.ACTIVE);
}
this._logGroup = props.logGroup;
resource.node.addDependency(this.role);
this.functionName = this.getResourceNameAttribute(resource.ref);
this.functionArn = this.getResourceArnAttribute(resource.attrArn, {
service: 'lambda',
resource: 'function',
resourceName: this.physicalName,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});
this.runtime = props.runtime;
this.timeout = props.timeout;
this.architecture = props.architecture ?? Architecture.X86_64;
if (props.layers) {
if (props.runtime === Runtime.FROM_IMAGE) {
throw new ValidationError('Layers are not supported for container image functions', this);
}
this.addLayers(...props.layers);
}
for (const event of props.events || []) {
this.addEventSource(event);
}
// Log retention
if (props.logRetention) {
if (props.logGroup) {
throw new ValidationError('CDK does not support setting logRetention and logGroup', this);
}
const logRetention = new logs.LogRetention(this, 'LogRetention', {
logGroupName: `/aws/lambda/${this.functionName}`,
retention: props.logRetention,
role: props.logRetentionRole,
logRetentionRetryOptions: props.logRetentionRetryOptions as logs.LogRetentionRetryOptions,
});
this._logGroup = logs.LogGroup.fromLogGroupArn(this, 'LogGroup', logRetention.logGroupArn);
this._logRetention = logRetention;
}
props.code.bindToResource(resource);
// Event Invoke Config
if (props.onFailure || props.onSuccess || props.maxEventAge || props.retryAttempts !== undefined) {
this.configureAsyncInvoke({
onFailure: props.onFailure,
onSuccess: props.onSuccess,
maxEventAge: props.maxEventAge,
retryAttempts: props.retryAttempts,
});
}
this.currentVersionOptions = props.currentVersionOptions;
if (props.filesystem) {
if (!props.vpc) {
throw new ValidationError('Cannot configure \'filesystem\' without configuring a VPC.', this);
}
const config = props.filesystem.config;
if (config.dependency) {
this.node.addDependency(...config.dependency);
}
// There could be a race if the Lambda is used in a CustomResource. It is possible for the Lambda to
// fail to attach to a given FileSystem if we do not have a dependency on the SecurityGroup ingress/egress
// rules that were created between this Lambda's SG & the Filesystem SG.
this.connections.securityGroups.forEach(sg => {
sg.node.findAll().forEach(child => {
if (child instanceof CfnResource && child.cfnResourceType === 'AWS::EC2::SecurityGroupEgress') {
resource.node.addDependency(child);
}
});
});
config.connections?.securityGroups.forEach(sg => {
sg.node.findAll().forEach(child => {
if (child instanceof CfnResource && child.cfnResourceType === 'AWS::EC2::SecurityGroupIngress') {
resource.node.addDependency(child);
}
});
});
}
// Configure Lambda insights
this.configureLambdaInsights(props);
this.configureAdotInstrumentation(props);
this.configureParamsAndSecretsExtension(props);
}