Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions apps/docs/content/docs/en/integrations/lambda.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Get a function's configuration, code location, tags, and reserved concurrency
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `configuration` | json | The function's configuration \(ARN, runtime, handler, memory, state, layers, VPC, and logging settings\) |
| `tagsError` | json | Why the tags could not be read, when a partial tag-read failure occurred |
| `code` | json | Presigned download URL for the deployment package, or the container image URI |
| `tags` | json | The function's tags |
| `reservedConcurrentExecutions` | number | Concurrency reserved for this function, if any |
Expand Down
15 changes: 13 additions & 2 deletions apps/sim/lib/api/contracts/tools/aws/lambda-create-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ const CreateAliasSchema = z.object({
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
aliasName: z.string().min(1, 'aliasName is required'),
aliasName: z
.string()
.min(1, 'aliasName is required')
.max(128, 'aliasName cannot exceed 128 characters')
.regex(
/^(?![0-9]+$)[a-zA-Z0-9-_]+$/,
'aliasName may only contain letters, numbers, hyphens, and underscores, and cannot be all digits'
),
aliasFunctionVersion: z.string().min(1, 'aliasFunctionVersion is required'),
description: z.string().optional(),
description: z.string().max(256, 'description cannot exceed 256 characters').optional(),
additionalVersionWeights: z
.record(
z.string().regex(/^[0-9]+$/, 'routing keys must be published version numbers'),
Expand All @@ -27,6 +34,10 @@ const CreateAliasSchema = z.object({
.min(0, 'a routing weight cannot be negative')
.max(1, 'a routing weight cannot exceed 1')
)
.refine(
(weights) => Object.keys(weights).length <= 1,
'additionalVersionWeights routes to a single second version, so it accepts at most one entry'
)
.optional(),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const CreateEventSourceMappingSchema = z
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
eventSourceArn: z.string().optional(),
eventSourceArn: z.string().min(1, 'eventSourceArn cannot be empty').optional(),
enabled: z.boolean().optional(),
batchSize: z.number().int().min(1).max(10000).optional(),
maximumBatchingWindowInSeconds: z.number().int().min(0).max(300).optional(),
Expand Down Expand Up @@ -54,16 +54,26 @@ const CreateEventSourceMappingSchema = z
documentDbFullDocument: z.enum(['UpdateLookup', 'Default']).optional(),
amazonManagedKafkaConsumerGroupId: z.string().optional(),
selfManagedKafkaConsumerGroupId: z.string().optional(),
selfManagedKafkaBootstrapServers: z.array(z.string()).optional(),
selfManagedKafkaBootstrapServers: z
.array(z.string().min(1, 'a bootstrap server cannot be empty'))
.optional(),
})
.superRefine((value, ctx) => {
if (!value.eventSourceArn && !value.selfManagedKafkaBootstrapServers?.length) {
const hasBootstrapServers = Boolean(value.selfManagedKafkaBootstrapServers?.length)
if (!value.eventSourceArn && !hasBootstrapServers) {
ctx.addIssue({
code: 'custom',
path: ['eventSourceArn'],
message:
'An event source is required: provide eventSourceArn, or selfManagedKafkaBootstrapServers for a self-managed Kafka cluster',
})
} else if (value.eventSourceArn && hasBootstrapServers) {
Comment thread
waleedlatif1 marked this conversation as resolved.
ctx.addIssue({
code: 'custom',
path: ['selfManagedKafkaBootstrapServers'],
message:
'A mapping has one event source: provide eventSourceArn, or selfManagedKafkaBootstrapServers, not both',
})
}
if (value.startingPosition === 'AT_TIMESTAMP' && !value.startingPositionTimestamp) {
ctx.addIssue({
Expand Down
58 changes: 37 additions & 21 deletions apps/sim/lib/api/contracts/tools/aws/lambda-create-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ const CreateFunctionSchema = z
runtime: z.string().optional(),
handler: z.string().optional(),
packageType: z.enum(['Zip', 'Image']).optional(),
s3Bucket: z.string().optional(),
s3Key: z.string().optional(),
s3ObjectVersion: z.string().optional(),
imageUri: z.string().optional(),
s3Bucket: z.string().min(1, 's3Bucket cannot be empty').optional(),
s3Key: z.string().min(1, 's3Key cannot be empty').optional(),
s3ObjectVersion: z.string().min(1, 's3ObjectVersion cannot be empty').optional(),
imageUri: z.string().min(1, 'imageUri cannot be empty').optional(),
sourceKmsKeyArn: z.string().optional(),
description: z.string().optional(),
description: z.string().max(256, 'description cannot exceed 256 characters').optional(),
functionTimeout: z.number().int().min(1).max(900).optional(),
memorySize: z.number().int().min(128).max(32768).optional(),
ephemeralStorageSize: z.number().int().min(512).max(10240).optional(),
Expand All @@ -48,36 +48,41 @@ const CreateFunctionSchema = z
logGroup: z.string().optional(),
})
.superRefine((value, ctx) => {
const hasAnyZipField = Boolean(
value.s3Bucket || value.s3Key || value.s3ObjectVersion || value.sourceKmsKeyArn
Comment thread
waleedlatif1 marked this conversation as resolved.
)
const hasS3 = Boolean(value.s3Bucket && value.s3Key)
if (!hasS3 && !value.imageUri) {
if (value.imageUri && hasAnyZipField) {
ctx.addIssue({
code: 'custom',
path: ['s3Bucket'],
path: ['imageUri'],
message:
'A code source is required: provide s3Bucket and s3Key for a .zip package, or imageUri for a container image',
'Provide either a .zip package (s3Bucket, s3Key, s3ObjectVersion, sourceKmsKeyArn) or imageUri, not both',
})
return
}
if (hasS3 && value.imageUri) {
if (!value.imageUri && !hasS3) {
ctx.addIssue({
code: 'custom',
path: ['imageUri'],
message: 'Provide either an S3 package or imageUri, not both',
path: hasAnyZipField ? ['s3Key'] : ['s3Bucket'],
message: hasAnyZipField
? 's3Bucket and s3Key must be provided together for a .zip package'
: 'A code source is required: provide s3Bucket and s3Key for a .zip package, or imageUri for a container image',
})
return
}
if (value.packageType === 'Image' && hasS3) {
if (value.imageUri && value.packageType === 'Zip') {
ctx.addIssue({
code: 'custom',
path: ['imageUri'],
message: 'packageType Image requires imageUri, not an S3 package',
path: ['packageType'],
message: 'packageType Zip requires an S3 package, not imageUri',
})
}
if (value.packageType === 'Zip' && value.imageUri) {
if (hasS3 && value.packageType === 'Image') {
ctx.addIssue({
code: 'custom',
path: ['s3Bucket'],
message: 'packageType Zip requires an S3 package, not imageUri',
path: ['imageUri'],
message: 'packageType Image requires imageUri, not an S3 package',
})
}
if (hasS3) {
Expand All @@ -96,15 +101,26 @@ const CreateFunctionSchema = z
})
}
}
const hasSubnets = value.vpcSubnetIds !== undefined
const hasSecurityGroups = value.vpcSecurityGroupIds !== undefined
if (hasSubnets !== hasSecurityGroups) {
const subnetIds = value.vpcSubnetIds
const securityGroupIds = value.vpcSecurityGroupIds
if ((subnetIds === undefined) !== (securityGroupIds === undefined)) {
ctx.addIssue({
code: 'custom',
path: [hasSubnets ? 'vpcSecurityGroupIds' : 'vpcSubnetIds'],
path: [subnetIds === undefined ? 'vpcSubnetIds' : 'vpcSecurityGroupIds'],
message:
'vpcSubnetIds and vpcSecurityGroupIds must be supplied together: send both lists to attach a VPC, or both empty to detach',
})
} else if (
subnetIds !== undefined &&
securityGroupIds !== undefined &&
(subnetIds.length === 0) !== (securityGroupIds.length === 0)
) {
ctx.addIssue({
code: 'custom',
path: [subnetIds.length === 0 ? 'vpcSubnetIds' : 'vpcSecurityGroupIds'],
message:
'vpcSubnetIds and vpcSecurityGroupIds must both be empty to detach, or both be populated to attach',
})
}
})

Expand Down
9 changes: 8 additions & 1 deletion apps/sim/lib/api/contracts/tools/aws/lambda-delete-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ const DeleteAliasSchema = z.object({
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
aliasName: z.string().min(1, 'aliasName is required'),
aliasName: z
.string()
.min(1, 'aliasName is required')
.max(128, 'aliasName cannot exceed 128 characters')
.regex(
/^(?![0-9]+$)[a-zA-Z0-9-_]+$/,
'aliasName may only contain letters, numbers, hyphens, and underscores, and cannot be all digits'
),
})

const DeleteAliasResponseSchema = lambdaMessageResponseSchema
Expand Down
9 changes: 8 additions & 1 deletion apps/sim/lib/api/contracts/tools/aws/lambda-get-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ const GetAliasSchema = z.object({
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
aliasName: z.string().min(1, 'aliasName is required'),
aliasName: z
.string()
.min(1, 'aliasName is required')
.max(128, 'aliasName cannot exceed 128 characters')
.regex(
/^(?![0-9]+$)[a-zA-Z0-9-_]+$/,
'aliasName may only contain letters, numbers, hyphens, and underscores, and cannot be all digits'
),
})

const GetAliasResponseSchema = z.object({
Expand Down
3 changes: 3 additions & 0 deletions apps/sim/lib/api/contracts/tools/aws/lambda-get-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const GetFunctionResponseSchema = z.object({
success: z.literal(true),
output: z.object({
configuration: lambdaFunctionConfigurationSchema.nullable(),
tagsError: z
.object({ errorCode: z.string().nullable(), message: z.string().nullable() })
.nullable(),
code: z
.object({
repositoryType: z.string().nullable(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { defineRouteContract } from '@/lib/api/contracts/types'

const GetLayerVersionSchema = z.object({
...lambdaConnectionFields,
layerName: z.string().min(1, 'layerName is required'),
layerName: z
.string()
.min(1, 'layerName is required')
.max(140, 'layerName cannot exceed 140 characters')
.regex(
/^(arn:[a-zA-Z0-9-]+:lambda:[a-zA-Z0-9-]+:\d{12}:layer:[a-zA-Z0-9-_]+)$|^[a-zA-Z0-9-_]+$/,
'layerName must be a layer name or a layer ARN'
),
versionNumber: z.number().int().min(1, 'versionNumber must be at least 1'),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
const ListEventSourceMappingsSchema = z.object({
...lambdaConnectionFields,
...lambdaPaginationFields,
functionName: z.string().optional(),
eventSourceArn: z.string().optional(),
functionName: z
.string()
.min(1, 'functionName cannot be empty')
.max(256, 'functionName cannot exceed 256 characters')
.optional(),
eventSourceArn: z.string().min(1, 'eventSourceArn cannot be empty').optional(),
})

const ListEventSourceMappingsResponseSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
const ListLayerVersionsSchema = z.object({
...lambdaConnectionFields,
...lambdaSmallPaginationFields,
layerName: z.string().min(1, 'layerName is required'),
layerName: z
.string()
.min(1, 'layerName is required')
.max(140, 'layerName cannot exceed 140 characters')
.regex(
/^(arn:[a-zA-Z0-9-]+:lambda:[a-zA-Z0-9-]+:\d{12}:layer:[a-zA-Z0-9-_]+)$|^[a-zA-Z0-9-_]+$/,
'layerName must be a layer name or a layer ARN'
),
compatibleRuntime: z.string().optional(),
compatibleArchitecture: z.enum(['x86_64', 'arm64']).optional(),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PublishVersionSchema = z.object({
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
codeSha256: z.string().optional(),
description: z.string().optional(),
description: z.string().max(256, 'description cannot exceed 256 characters').optional(),
revisionId: z.string().optional(),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ const RemovePermissionSchema = z.object({
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
statementId: z.string().min(1, 'statementId is required'),
statementId: z
.string()
.min(1, 'statementId is required')
.max(100, 'statementId cannot exceed 100 characters')
.regex(
/^[a-zA-Z0-9-_.]+$/,
'statementId may only contain letters, numbers, hyphens, underscores, and dots'
),
qualifier: z
.string()
.min(1, 'qualifier cannot be empty')
Expand Down
13 changes: 7 additions & 6 deletions apps/sim/lib/api/contracts/tools/aws/lambda-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export const lambdaFunctionConfigurationSchema = z.object({
),
fileSystemConfigs: z.array(
z.object({
arn: z.string(),
localMountPath: z.string(),
arn: z.string().nullable(),
localMountPath: z.string().nullable(),
})
),
vpcConfig: z
Expand Down Expand Up @@ -249,6 +249,7 @@ export const lambdaEventSourceMappingSchema = z.object({
fullDocument: z.string().nullable(),
})
.nullable(),
selfManagedKafkaBootstrapServers: z.array(z.string()),
provisionedPollerConfig: z
.object({
minimumPollers: z.number().nullable(),
Expand All @@ -260,10 +261,10 @@ export const lambdaEventSourceMappingSchema = z.object({

/** Camel-cased projection of the Lambda `FunctionUrlConfig` data type. */
export const lambdaFunctionUrlConfigSchema = z.object({
functionUrl: z.string(),
functionArn: z.string(),
authType: z.string(),
creationTime: z.string(),
functionUrl: z.string().nullable(),
functionArn: z.string().nullable(),
authType: z.string().nullable(),
creationTime: z.string().nullable(),
lastModifiedTime: z.string().nullable(),
invokeMode: z.string().nullable(),
cors: z
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/lib/api/contracts/tools/aws/lambda-tag-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
const TagResourceSchema = z.object({
...lambdaConnectionFields,
resourceArn: z.string().min(1, 'resourceArn is required'),
tags: z.record(z.string(), z.string()),
tags: z
.record(z.string().min(1, 'a tag key cannot be empty'), z.string())
.refine((tags) => Object.keys(tags).length > 0, 'tags must contain at least one entry'),
})

const TagResourceResponseSchema = lambdaMessageResponseSchema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
const UntagResourceSchema = z.object({
...lambdaConnectionFields,
resourceArn: z.string().min(1, 'resourceArn is required'),
tagKeys: z.array(z.string()).min(1, 'tagKeys must contain at least one key'),
tagKeys: z
.array(z.string().min(1, 'a tag key cannot be empty'))
.min(1, 'tagKeys must contain at least one key'),
})

const UntagResourceResponseSchema = lambdaMessageResponseSchema
Expand Down
15 changes: 13 additions & 2 deletions apps/sim/lib/api/contracts/tools/aws/lambda-update-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ const UpdateAliasSchema = z.object({
.string()
.min(1, 'functionName is required')
.max(256, 'functionName cannot exceed 256 characters'),
aliasName: z.string().min(1, 'aliasName is required'),
aliasName: z
.string()
.min(1, 'aliasName is required')
.max(128, 'aliasName cannot exceed 128 characters')
.regex(
/^(?![0-9]+$)[a-zA-Z0-9-_]+$/,
'aliasName may only contain letters, numbers, hyphens, and underscores, and cannot be all digits'
),
aliasFunctionVersion: z.string().optional(),
description: z.string().optional(),
description: z.string().max(256, 'description cannot exceed 256 characters').optional(),
additionalVersionWeights: z
.record(
z.string().regex(/^[0-9]+$/, 'routing keys must be published version numbers'),
Expand All @@ -27,6 +34,10 @@ const UpdateAliasSchema = z.object({
.min(0, 'a routing weight cannot be negative')
.max(1, 'a routing weight cannot exceed 1')
)
.refine(
(weights) => Object.keys(weights).length <= 1,
'additionalVersionWeights routes to a single second version, so it accepts at most one entry'
)
.optional(),
revisionId: z.string().optional(),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
const UpdateEventSourceMappingSchema = z.object({
...lambdaConnectionFields,
uuid: z.string().min(1, 'uuid is required'),
functionName: z.string().optional(),
functionName: z
.string()
.min(1, 'functionName cannot be empty')
.max(256, 'functionName cannot exceed 256 characters')
.optional(),
enabled: z.boolean().optional(),
batchSize: z.number().int().min(1).max(10000).optional(),
maximumBatchingWindowInSeconds: z.number().int().min(0).max(300).optional(),
Expand Down
Loading
Loading