aws-samples / prisma-lambda-cdk

Build and deploy a Lambda function with Prisma ORM by AWS Cloud Development Kit.
MIT No Attribution
97 stars 25 forks source link

AWS asset duplication #27

Open hecarrillo opened 2 months ago

hecarrillo commented 2 months ago

I have a setup based on this template. However, it seems that the setup only works if I create the function construct and also the docker version of the same function, resulting in the same function being deployed twice to AWS. Which would be the appropriate way of handling a multi-function setup given that I only use the docker setup?

The setup I currently use looks like this:

import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import { DatabaseConnectionProps, PrismaFunction } from "./prisma-function";
import { Construct } from "constructs";
import { DockerPrismaFunction } from "./docker-prisma-function";
import { DockerImageCode, Function, Runtime } from "aws-cdk-lib/aws-lambda";
import { Platform } from "aws-cdk-lib/aws-ecr-assets";
import { InvocationType, Trigger } from "aws-cdk-lib/triggers";
import { UserPool, UserPoolClient } from "aws-cdk-lib/aws-cognito";
import { ResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront";
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';

interface ApplicationProps {
  vpc: ec2.IVpc;
  database: DatabaseConnectionProps;
  userPool: UserPool;
  userPoolClient: UserPoolClient;
  blogPostBucket: s3.IBucket;
  cloudfrontDistribution: cloudfront.IDistribution;
}

export class Application extends Construct {
  readonly lambdaSecurityGroup: ec2.ISecurityGroup;
  readonly migrationHandler: Function;
  readonly userAPI: apigateway.LambdaRestApi;

  constructor(scope: Construct, id: string, props: ApplicationProps) {
    super(scope, id);

    const { vpc, database, userPool, userPoolClient } = props;

    const securityGroup = new ec2.SecurityGroup(this, `SecurityGroup`, {
      vpc: props.vpc,
    });

    // Create a Cognito User Pool Authorizer
    const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
      cognitoUserPools: [props.userPool]
    });

    // Create an API Gateway with API Key authentication
    const api = new apigateway.RestApi(this, 'ra-api', {
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS, 
        allowHeaders: apigateway.Cors.DEFAULT_HEADERS.concat(['x-api-key', 'Authorization'])
      }
    });

    const migrationRunner = new PrismaFunction(this, "MigrationRunner", {
      entry: "./app/src/migration/migration-runner.ts",
      memorySize: 256,
      runtime: Runtime.NODEJS_20_X,
      timeout: cdk.Duration.minutes(1),
      vpc,
      securityGroups: [securityGroup],
      database,
      depsLockFilePath: "./app/package-lock.json",
    });

    new DockerPrismaFunction(this, "DockerMigrationRunner", {
      code: DockerImageCode.fromImageAsset("./app", {
        cmd: ["migration-runner.handler"],
        platform: Platform.LINUX_ARM64,
      }),
      memorySize: 256,
      timeout: cdk.Duration.minutes(1),
      vpc,
      securityGroups: [securityGroup],
      database,
    });

    const generatePresignedUrlHandler = new PrismaFunction(this, "GeneratePresignedUrlHandler", {
      entry: "./app/src/img/generatePresignedUrl.ts",
      memorySize: 256,
      runtime: Runtime.NODEJS_20_X,
      timeout: cdk.Duration.minutes(1),
      vpc,
      securityGroups: [securityGroup],
      database,
      depsLockFilePath: "./app/package-lock.json",
      environment: {
        BUCKET_NAME: props.blogPostBucket.bucketName,
        CLOUDFRONT_DOMAIN: props.cloudfrontDistribution.distributionDomainName,
      },
    });

    new DockerPrismaFunction(this, "DockerGeneratePresignedUrlHandler", {
      code: DockerImageCode.fromImageAsset("./app", {
        cmd: ["generatePresignedUrl.handler"],
        platform: Platform.LINUX_ARM64,
      }),
      memorySize: 256,
      timeout: cdk.Duration.minutes(1),
      vpc,
      securityGroups: [securityGroup],
      database,
      environment: {
        BUCKET_NAME: props.blogPostBucket.bucketName,
        CLOUDFRONT_DOMAIN: props.cloudfrontDistribution.distributionDomainName,
      },
    });

    props.blogPostBucket.grantReadWrite(generatePresignedUrlHandler);

    // img/presigned-url
    const img = api.root.addResource('img');
    const presignedUrl = img.addResource('presigned-url');

    presignedUrl.addMethod('GET', new apigateway.LambdaIntegration(generatePresignedUrlHandler), {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    });

    const userHandler = new PrismaFunction(this, "User", {
      entry: "./app/src/user/userIndex.ts", 
      memorySize: 256,
      runtime: Runtime.NODEJS_20_X,
      timeout: cdk.Duration.seconds(30),
      vpc,
      securityGroups: [securityGroup],
      database,
      depsLockFilePath: "./app/package-lock.json",
      environment: {
        COGNITO_USER_POOL_ID: userPool.userPoolId,
        COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
      },
    });

    new DockerPrismaFunction(this, "UserHandler", {
      code: DockerImageCode.fromImageAsset("./app", {
        cmd: ["userIndex.handler"],
        platform: Platform.LINUX_ARM64,
      }),
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
      vpc,
      securityGroups: [securityGroup],
      database,
      environment: {
        COGNITO_USER_POOL_ID: userPool.userPoolId,
        COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
      },
    });

    userHandler.addToRolePolicy(new iam.PolicyStatement({
      actions: [
        "cognito-idp:AdminCreateUser",
        "cognito-idp:AdminUpdateUserAttributes",
        "cognito-idp:AdminGetUser",
      ],
      resources: [props.userPool.userPoolArn],
    }));

    const user = api.root.addResource('user');

    const user_me = user.addResource('me');
    user_me.addMethod('GET', new apigateway.LambdaIntegration(userHandler), {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO, 
    });
    user.addMethod('PUT', new apigateway.LambdaIntegration(userHandler), {
      apiKeyRequired: false,
    });
    user.addMethod('POST', new apigateway.LambdaIntegration(userHandler), {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    });

    const blogHandler = new PrismaFunction(this, "BlogHandler", {
      entry: "./app/src/blog/blogIndex.ts",
      memorySize: 256,
      runtime: Runtime.NODEJS_20_X,
      timeout: cdk.Duration.seconds(30),
      vpc,
      securityGroups: [securityGroup],
      database,
      depsLockFilePath: "./app/package-lock.json",
      environment: {
        COGNITO_USER_POOL_ID: userPool.userPoolId,
        COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
      },
    });

    // Add Docker version of the blog handler
    new DockerPrismaFunction(this, "DockerBlogHandler", {
      code: DockerImageCode.fromImageAsset("./app", {
        cmd: ["blogIndex.handler"],
        platform: Platform.LINUX_ARM64,
      }),
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
      vpc,
      securityGroups: [securityGroup],
      database,
      environment: {
        COGNITO_USER_POOL_ID: userPool.userPoolId,
        COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
      },
    });

    // Add blog resource to API
    const blog = api.root.addResource('blog');

    // Add POST method for blog creation
    blog.addMethod('POST', new apigateway.LambdaIntegration(blogHandler), {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    });

    const plan = api.addUsagePlan('UsagePlan', {
      name: 'StandardUsagePlan',
      throttle: {
        rateLimit: 10,
        burstLimit: 10
      },
    });

    const apiKey = api.addApiKey('UsersApiKey');
    plan.addApiKey(apiKey);

    new cdk.CfnOutput(this, `MigrationRunnerLambdaArn`, { value: migrationRunner.functionArn });
    new cdk.CfnOutput(this, `UserApiUrl`, { value: api.url });
    new cdk.CfnOutput(this, `UserApiKey`, { 
      value: apiKey.keyId,
      description: 'API Key ID for Users API'
    });

    this.lambdaSecurityGroup = securityGroup;
    this.migrationHandler = migrationRunner;
    this.userAPI = api;
  }
}
tmokmss commented 3 days ago

Hi @hecarrillo sorry for the late reply!

This repository indeed contains two Lambda deployment pattern with zip and Docker deployment for educational purpose. If you want to use Docker deployment, you can remove PrismaFunction code entirely.