Initial version of lambda creation works

This commit is contained in:
Siddharth Ganesan
2025-07-07 12:34:21 -07:00
parent abf1ac06ce
commit 46be9e3558
6 changed files with 339 additions and 2 deletions

View File

@@ -0,0 +1,183 @@
import { NextRequest, NextResponse } from 'next/server'
import { LambdaClient, CreateFunctionCommand, UpdateFunctionCodeCommand, GetFunctionCommand, Runtime } from '@aws-sdk/client-lambda'
import JSZip from 'jszip'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console-logger'
const logger = createLogger('AWSLambdaDeployAPI')
// Validation schema for the request body
const DeployRequestSchema = z.object({
accessKeyId: z.string().min(1, 'AWS Access Key ID is required'),
secretAccessKey: z.string().min(1, 'AWS Secret Access Key is required'),
region: z.string().min(1, 'AWS Region is required'),
functionName: z.string().min(1, 'Function name is required'),
handler: z.string().optional(),
runtime: z.string().min(1, 'Runtime is required'),
code: z.string().min(1, 'Function code is required'),
requirements: z.string().nullable().optional(),
packageJson: z.string().nullable().optional(),
timeout: z.number().min(1).max(900),
memorySize: z.number().min(128).max(10240),
environmentVariables: z.record(z.string()).default({}),
tags: z.record(z.string()).default({}),
role: z.string().min(1, 'Role ARN is required'),
})
export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID().slice(0, 8)
try {
logger.info(`[${requestId}] Processing AWS Lambda deployment request`)
// Parse and validate request body
const body = await request.json()
const validationResult = DeployRequestSchema.safeParse(body)
if (!validationResult.success) {
logger.warn(`[${requestId}] Invalid request body`, { errors: validationResult.error.errors })
return NextResponse.json(
{ error: 'Invalid request parameters', details: validationResult.error.errors },
{ status: 400 }
)
}
const params = validationResult.data
console.log(`[${requestId}] Received params:`, JSON.stringify(params, null, 2))
logger.info(`[${requestId}] Deploying Lambda function: ${params.functionName}`)
// Create Lambda client
const lambdaClient = new LambdaClient({
region: params.region,
credentials: {
accessKeyId: params.accessKeyId,
secretAccessKey: params.secretAccessKey,
},
})
// Create ZIP file with the Lambda code and dependencies
const zip = new JSZip()
// Add the main function code
const fileExtension = getFileExtension(params.runtime)
const fileName = `index.${fileExtension}`
zip.file(fileName, params.code)
// Add dependencies based on runtime
if (params.runtime.startsWith('python') && params.requirements && params.requirements.trim() !== '') {
zip.file('requirements.txt', params.requirements)
} else if (params.runtime.startsWith('nodejs') && params.packageJson && params.packageJson.trim() !== '') {
zip.file('package.json', params.packageJson)
}
const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' })
// Check if function already exists
let functionExists = false
try {
await lambdaClient.send(new GetFunctionCommand({ FunctionName: params.functionName }))
functionExists = true
logger.info(`[${requestId}] Function ${params.functionName} already exists, updating code`)
} catch (error: any) {
if (error.name === 'ResourceNotFoundException') {
functionExists = false
logger.info(`[${requestId}] Function ${params.functionName} does not exist, creating new function`)
} else {
throw error
}
}
let result: any
if (functionExists) {
// Update existing function code
const updateParams = {
FunctionName: params.functionName,
ZipFile: zipBuffer,
}
result = await lambdaClient.send(new UpdateFunctionCodeCommand(updateParams))
logger.info(`[${requestId}] Lambda function code updated: ${result.FunctionArn}`)
} else {
// Create new function
if (!params.role) {
throw new Error('Role ARN is required for creating new Lambda functions. Please provide a valid IAM Role ARN.')
}
const createParams = {
FunctionName: params.functionName,
Runtime: params.runtime as Runtime,
Role: params.role,
Handler: params.handler || getDefaultHandler(params.runtime),
Code: {
ZipFile: zipBuffer,
},
Timeout: params.timeout,
MemorySize: params.memorySize,
Environment: {
Variables: params.environmentVariables,
},
Tags: params.tags,
}
result = await lambdaClient.send(new CreateFunctionCommand(createParams))
logger.info(`[${requestId}] Lambda function created: ${result.FunctionArn}`)
}
// Get function details for response
const functionDetails = await lambdaClient.send(
new GetFunctionCommand({ FunctionName: params.functionName })
)
const response = {
functionArn: functionDetails.Configuration?.FunctionArn || '',
functionName: functionDetails.Configuration?.FunctionName || '',
runtime: functionDetails.Configuration?.Runtime || '',
region: params.region,
status: functionDetails.Configuration?.State || '',
lastModified: functionDetails.Configuration?.LastModified || '',
codeSize: functionDetails.Configuration?.CodeSize || 0,
description: functionDetails.Configuration?.Description || '',
timeout: functionDetails.Configuration?.Timeout || 0,
memorySize: functionDetails.Configuration?.MemorySize || 0,
environment: functionDetails.Configuration?.Environment?.Variables || {},
tags: functionDetails.Tags || {},
}
return NextResponse.json({
success: true,
output: response,
})
} catch (error) {
logger.error(`[${requestId}] Error deploying Lambda function`, error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to deploy Lambda function'
},
{ status: 500 }
)
}
}
// Helper functions
function getFileExtension(runtime: string): string {
if (runtime.startsWith('nodejs')) return 'js'
if (runtime.startsWith('python')) return 'py'
if (runtime.startsWith('java')) return 'java'
if (runtime.startsWith('dotnet')) return 'cs'
if (runtime.startsWith('go')) return 'go'
if (runtime.startsWith('ruby')) return 'rb'
return 'js' // default
}
function getDefaultHandler(runtime: string): string {
if (runtime.startsWith('nodejs')) return 'index.handler'
if (runtime.startsWith('python')) return 'index.lambda_handler'
if (runtime.startsWith('java')) return 'com.example.LambdaFunction::handleRequest'
if (runtime.startsWith('dotnet')) return 'LambdaFunction::LambdaFunction.Function::FunctionHandler'
if (runtime.startsWith('go')) return 'main'
if (runtime.startsWith('ruby')) return 'index.lambda_handler'
return 'index.handler' // default
}

View File

@@ -77,6 +77,14 @@ export const AWSLambdaBlock: BlockConfig<AWSLambdaResponse> = {
'sa-east-1',
],
},
{
id: 'role',
title: 'Role ARN',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the IAM Role ARN for Lambda execution',
password: false,
},
{
id: 'functionName',
title: 'Function Name',
@@ -195,6 +203,7 @@ export const AWSLambdaBlock: BlockConfig<AWSLambdaResponse> = {
accessKeyId: { type: 'string', required: true },
secretAccessKey: { type: 'string', required: true },
region: { type: 'string', required: true },
role: { type: 'string', required: true },
functionName: { type: 'string', required: true },
handler: { type: 'string', required: false },
runtime: { type: 'string', required: true },

View File

@@ -27,8 +27,9 @@
},
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/client-s3": "^3.779.0",
"@aws-sdk/s3-request-presigner": "^3.779.0",
"@aws-sdk/client-lambda": "3.840.0",
"@aws-sdk/client-s3": "3.842.0",
"@aws-sdk/s3-request-presigner": "3.842.0",
"@azure/storage-blob": "12.27.0",
"@better-auth/stripe": "^1.2.9",
"@browserbasehq/stagehand": "^2.0.0",
@@ -69,6 +70,7 @@
"@vercel/og": "^0.6.5",
"@vercel/speed-insights": "^1.2.0",
"ai": "^4.3.2",
"archiver": "7.0.1",
"better-auth": "^1.2.9",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.1",
@@ -86,6 +88,7 @@
"input-otp": "^1.4.2",
"ioredis": "^5.6.0",
"jose": "6.0.11",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"lenis": "^1.2.3",
"lucide-react": "^0.479.0",

View File

@@ -0,0 +1,139 @@
import type { ToolConfig } from '../types'
interface AWSLambdaDeployInput {
accessKeyId: string
secretAccessKey: string
region: string
functionName: string
handler?: string
runtime: string
code: string
requirements?: string
packageJson?: string
timeout: number
memorySize: number
environmentVariables: Record<string, string>
tags: Record<string, string>
role: string
}
interface AWSLambdaDeployOutput {
functionArn: string
functionName: string
runtime: string
region: string
status: string
lastModified: string
codeSize: number
description: string
timeout: number
memorySize: number
environment: Record<string, string>
tags: Record<string, string>
}
export const awsLambdaDeployTool: ToolConfig<AWSLambdaDeployInput, AWSLambdaDeployOutput> = {
id: 'aws_lambda_deploy',
name: 'AWS Lambda Deploy',
description: 'Deploy or update an AWS Lambda function with the specified configuration',
version: '1.0.0',
params: {
accessKeyId: {
type: 'string',
required: true,
description: 'AWS Access Key ID for authentication',
},
secretAccessKey: {
type: 'string',
required: true,
description: 'AWS Secret Access Key for authentication',
},
region: {
type: 'string',
required: true,
description: 'AWS region where the Lambda function will be deployed',
},
functionName: {
type: 'string',
required: true,
description: 'Name of the Lambda function to create or update',
},
handler: {
type: 'string',
required: false,
description: 'Function handler (e.g., index.handler)',
},
runtime: {
type: 'string',
required: true,
description: 'Lambda runtime (e.g., nodejs18.x, python3.11)',
},
code: {
type: 'string',
required: true,
description: 'Function code to deploy',
},
requirements: {
type: 'string',
required: false,
description: 'Python requirements.txt content',
},
packageJson: {
type: 'string',
required: false,
description: 'Node.js package.json content',
},
timeout: {
type: 'number',
required: true,
description: 'Function timeout in seconds (1-900)',
},
memorySize: {
type: 'number',
required: true,
description: 'Function memory size in MB (128-10240)',
},
environmentVariables: {
type: 'object',
required: false,
description: 'Environment variables for the function',
default: {},
},
tags: {
type: 'object',
required: false,
description: 'Tags for the function',
default: {},
},
role: {
type: 'string',
required: true,
description: 'IAM Role ARN for Lambda execution',
},
},
request: {
url: '/api/tools/aws-lambda/deploy',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: AWSLambdaDeployInput) => ({
accessKeyId: params.accessKeyId,
secretAccessKey: params.secretAccessKey,
region: params.region,
functionName: params.functionName,
handler: params.handler,
runtime: params.runtime,
code: params.code,
requirements: params.requirements,
packageJson: params.packageJson,
timeout: params.timeout,
memorySize: params.memorySize,
environmentVariables: params.environmentVariables || {},
tags: params.tags || {},
role: params.role,
}),
},
}

View File

@@ -0,0 +1 @@
export { awsLambdaDeployTool } from './deploy'

View File

@@ -103,6 +103,7 @@ import { whatsappSendMessageTool } from './whatsapp'
import { workflowExecutorTool } from './workflow'
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from './x'
import { youtubeSearchTool } from './youtube'
import { awsLambdaDeployTool } from './aws_lambda'
// Registry of all available tools
export const tools: Record<string, ToolConfig> = {
@@ -223,4 +224,5 @@ export const tools: Record<string, ToolConfig> = {
google_calendar_quick_add: googleCalendarQuickAddTool,
google_calendar_invite: googleCalendarInviteTool,
workflow_executor: workflowExecutorTool,
aws_lambda_deploy: awsLambdaDeployTool,
}