image processing lambda
This commit is contained in:
219
infrastructure/cdk/lib/image-processor-lambda-stack.ts
Normal file
219
infrastructure/cdk/lib/image-processor-lambda-stack.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import * as cdk from "aws-cdk-lib";
|
||||
import * as lambda from "aws-cdk-lib/aws-lambda";
|
||||
import * as iam from "aws-cdk-lib/aws-iam";
|
||||
import * as s3 from "aws-cdk-lib/aws-s3";
|
||||
import * as s3n from "aws-cdk-lib/aws-s3-notifications";
|
||||
import * as sqs from "aws-cdk-lib/aws-sqs";
|
||||
import { Construct } from "constructs";
|
||||
import * as path from "path";
|
||||
|
||||
interface ImageProcessorLambdaStackProps extends cdk.StackProps {
|
||||
/**
|
||||
* Environment name (staging, prod)
|
||||
*/
|
||||
environment: string;
|
||||
|
||||
/**
|
||||
* Database URL for the Lambda
|
||||
*/
|
||||
databaseUrl: string;
|
||||
|
||||
/**
|
||||
* Frontend URL for CORS configuration
|
||||
*/
|
||||
frontendUrl: string;
|
||||
}
|
||||
|
||||
export class ImageProcessorLambdaStack extends cdk.Stack {
|
||||
/**
|
||||
* The Lambda function for image processing
|
||||
*/
|
||||
public readonly lambdaFunction: lambda.Function;
|
||||
|
||||
/**
|
||||
* The S3 bucket for image uploads
|
||||
*/
|
||||
public readonly uploadsBucket: s3.Bucket;
|
||||
|
||||
/**
|
||||
* Dead letter queue for failed Lambda invocations
|
||||
*/
|
||||
public readonly deadLetterQueue: sqs.Queue;
|
||||
|
||||
constructor(
|
||||
scope: Construct,
|
||||
id: string,
|
||||
props: ImageProcessorLambdaStackProps
|
||||
) {
|
||||
super(scope, id, props);
|
||||
|
||||
const { environment, databaseUrl, frontendUrl } = props;
|
||||
|
||||
// Dead Letter Queue for failed Lambda invocations
|
||||
this.deadLetterQueue = new sqs.Queue(this, "ImageProcessorDLQ", {
|
||||
queueName: `image-processor-dlq-${environment}`,
|
||||
retentionPeriod: cdk.Duration.days(14),
|
||||
});
|
||||
|
||||
// S3 bucket for uploads
|
||||
this.uploadsBucket = new s3.Bucket(this, "UploadsBucket", {
|
||||
bucketName: `village-share-${environment}`,
|
||||
versioned: true,
|
||||
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||
blockPublicAccess: new s3.BlockPublicAccess({
|
||||
blockPublicAcls: true,
|
||||
blockPublicPolicy: false, // Allow bucket policy for public reads
|
||||
ignorePublicAcls: true,
|
||||
restrictPublicBuckets: false,
|
||||
}),
|
||||
cors: [
|
||||
{
|
||||
allowedMethods: [
|
||||
s3.HttpMethods.GET,
|
||||
s3.HttpMethods.PUT,
|
||||
s3.HttpMethods.POST,
|
||||
],
|
||||
allowedOrigins: [frontendUrl, "http://localhost:3000"],
|
||||
allowedHeaders: ["*"],
|
||||
exposedHeaders: ["ETag"],
|
||||
maxAge: 3600,
|
||||
},
|
||||
],
|
||||
lifecycleRules: [
|
||||
{
|
||||
// Clean up incomplete multipart uploads
|
||||
abortIncompleteMultipartUploadAfter: cdk.Duration.days(1),
|
||||
},
|
||||
{
|
||||
// Delete staging files that weren't processed after 7 days
|
||||
prefix: "staging/",
|
||||
expiration: cdk.Duration.days(7),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Bucket policy: allow public read for non-staging files
|
||||
this.uploadsBucket.addToResourcePolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
principals: [new iam.AnyPrincipal()],
|
||||
actions: ["s3:GetObject"],
|
||||
resources: [
|
||||
`${this.uploadsBucket.bucketArn}/profiles/*`,
|
||||
`${this.uploadsBucket.bucketArn}/items/*`,
|
||||
`${this.uploadsBucket.bucketArn}/forum/*`,
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Lambda execution role
|
||||
const lambdaRole = new iam.Role(this, "ImageProcessorLambdaRole", {
|
||||
roleName: `image-processor-lambda-role-${environment}`,
|
||||
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
|
||||
description: "Execution role for Image Processor Lambda",
|
||||
});
|
||||
|
||||
// CloudWatch Logs permissions
|
||||
lambdaRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents",
|
||||
],
|
||||
resources: ["*"],
|
||||
})
|
||||
);
|
||||
|
||||
// S3 permissions
|
||||
lambdaRole.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
effect: iam.Effect.ALLOW,
|
||||
actions: [
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:HeadObject",
|
||||
],
|
||||
resources: [`${this.uploadsBucket.bucketArn}/*`],
|
||||
})
|
||||
);
|
||||
|
||||
// Lambda function
|
||||
this.lambdaFunction = new lambda.Function(this, "ImageProcessorLambda", {
|
||||
functionName: `image-processor-${environment}`,
|
||||
runtime: lambda.Runtime.NODEJS_20_X,
|
||||
handler: "index.handler",
|
||||
code: lambda.Code.fromAsset(
|
||||
path.join(__dirname, "../../../lambdas/imageProcessor"),
|
||||
{
|
||||
bundling: {
|
||||
image: lambda.Runtime.NODEJS_20_X.bundlingImage,
|
||||
command: [
|
||||
"bash",
|
||||
"-c",
|
||||
[
|
||||
"cp -r /asset-input/* /asset-output/",
|
||||
"cd /asset-output",
|
||||
"npm install --omit=dev",
|
||||
// Copy shared modules
|
||||
"mkdir -p shared",
|
||||
"cp -r /asset-input/../shared/* shared/",
|
||||
"cd shared && npm install --omit=dev",
|
||||
].join(" && "),
|
||||
],
|
||||
},
|
||||
}
|
||||
),
|
||||
role: lambdaRole,
|
||||
timeout: cdk.Duration.seconds(60),
|
||||
memorySize: 1024, // Higher memory for image processing
|
||||
environment: {
|
||||
NODE_ENV: environment,
|
||||
DATABASE_URL: databaseUrl,
|
||||
S3_BUCKET: this.uploadsBucket.bucketName,
|
||||
AWS_REGION: this.region,
|
||||
LOG_LEVEL: environment === "prod" ? "info" : "debug",
|
||||
},
|
||||
deadLetterQueue: this.deadLetterQueue,
|
||||
retryAttempts: 2,
|
||||
description:
|
||||
"Processes uploaded images: extracts metadata and strips EXIF",
|
||||
});
|
||||
|
||||
// S3 event notification for staging uploads
|
||||
this.uploadsBucket.addEventNotification(
|
||||
s3.EventType.OBJECT_CREATED,
|
||||
new s3n.LambdaDestination(this.lambdaFunction),
|
||||
{
|
||||
prefix: "staging/",
|
||||
}
|
||||
);
|
||||
|
||||
// Outputs
|
||||
new cdk.CfnOutput(this, "LambdaFunctionArn", {
|
||||
value: this.lambdaFunction.functionArn,
|
||||
description: "ARN of the Image Processor Lambda",
|
||||
exportName: `ImageProcessorLambdaArn-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "UploadsBucketName", {
|
||||
value: this.uploadsBucket.bucketName,
|
||||
description: "Name of the uploads S3 bucket",
|
||||
exportName: `UploadsBucketName-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "UploadsBucketArn", {
|
||||
value: this.uploadsBucket.bucketArn,
|
||||
description: "ARN of the uploads S3 bucket",
|
||||
exportName: `UploadsBucketArn-${environment}`,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, "DLQUrl", {
|
||||
value: this.deadLetterQueue.queueUrl,
|
||||
description: "URL of the Dead Letter Queue",
|
||||
exportName: `ImageProcessorDLQUrl-${environment}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user