From 9a16c5db8a4a12cffe5060e62e703687b5f5f96a Mon Sep 17 00:00:00 2001 From: hrodmn Date: Thu, 25 Sep 2025 13:42:20 -0500 Subject: [PATCH] fix: refactor Lambda constructs to only build custom image if provided resolves #87 --- lib/database/index.ts | 15 +++++++++++---- lib/ingestor-api/index.ts | 19 ++++++++++++++----- lib/stac-api/index.ts | 20 +++++++++++++------- lib/stac-auth-proxy/index.ts | 9 ++++++--- lib/stac-loader/index.ts | 9 ++++++--- lib/tipg-api/index.ts | 20 +++++++++++++------- lib/titiler-pgstac-api/index.ts | 20 +++++++++++++------- lib/utils/index.ts | 27 +++++++++++++++++++++++++++ 8 files changed, 103 insertions(+), 36 deletions(-) diff --git a/lib/database/index.ts b/lib/database/index.ts index 285a56bd..88a1a75d 100644 --- a/lib/database/index.ts +++ b/lib/database/index.ts @@ -10,7 +10,11 @@ import { aws_secretsmanager as secretsmanager, } from "aws-cdk-lib"; import { Construct } from "constructs"; -import { CustomLambdaFunctionProps, DEFAULT_PGSTAC_VERSION } from "../utils"; +import { + CustomLambdaFunctionProps, + DEFAULT_PGSTAC_VERSION, + resolveLambdaCode, +} from "../utils"; import { PgBouncer } from "./PgBouncer"; const instanceSizes: Record = require("./instance-memory.json"); @@ -111,6 +115,9 @@ export class PgStacDatabase extends Construct { this.pgstacVersion = props.pgstacVersion || DEFAULT_PGSTAC_VERSION; + const { code: userCode, ...otherLambdaOptions } = + props.bootstrapperLambdaFunctionOptions || {}; + const handler = new aws_lambda.Function(this, "lambda", { // defaults runtime: aws_lambda.Runtime.PYTHON_3_12, @@ -118,7 +125,7 @@ export class PgStacDatabase extends Construct { memorySize: 128, logRetention: aws_logs.RetentionDays.ONE_WEEK, timeout: Duration.minutes(2), - code: aws_lambda.Code.fromDockerBuild(__dirname, { + code: resolveLambdaCode(userCode, __dirname, { file: "bootstrapper_runtime/Dockerfile", buildArgs: { PYTHON_VERSION: "3.12", @@ -128,7 +135,7 @@ export class PgStacDatabase extends Construct { vpc: hasVpc(this.db) ? this.db.vpc : props.vpc, allowPublicSubnet: true, // overwrites defaults with user-provided configurable properties, - ...props.bootstrapperLambdaFunctionOptions, + ...otherLambdaOptions, }); this.pgstacSecret = new secretsmanager.Secret(this, "bootstrappersecret", { @@ -172,7 +179,7 @@ export class PgStacDatabase extends Construct { this.pgstacSecret.secretArn; // if props.lambdaFunctionOptions doesn't have 'code' defined, update pgstac_version (needed for default runtime) - if (!props.bootstrapperLambdaFunctionOptions?.code) { + if (!userCode) { customResourceProperties["pgstac_version"] = this.pgstacVersion; } diff --git a/lib/ingestor-api/index.ts b/lib/ingestor-api/index.ts index 0ce3dbeb..00842b1c 100644 --- a/lib/ingestor-api/index.ts +++ b/lib/ingestor-api/index.ts @@ -13,7 +13,11 @@ import { Stack, } from "aws-cdk-lib"; import { Construct } from "constructs"; -import { CustomLambdaFunctionProps, DEFAULT_PGSTAC_VERSION } from "../utils"; +import { + CustomLambdaFunctionProps, + DEFAULT_PGSTAC_VERSION, + resolveLambdaCode, +} from "../utils"; export class StacIngestor extends Construct { table: dynamodb.Table; @@ -116,6 +120,9 @@ export class StacIngestor extends Construct { lambdaFunctionOptions?: CustomLambdaFunctionProps; pgstacVersion?: string; }): lambda.Function { + const { code: userCode, ...otherLambdaOptions } = + props.lambdaFunctionOptions || {}; + const handler = new lambda.Function(this, "api-handler", { // defaults runtime: lambda.Runtime.PYTHON_3_12, @@ -123,7 +130,7 @@ export class StacIngestor extends Construct { memorySize: 2048, logRetention: aws_logs.RetentionDays.ONE_WEEK, timeout: Duration.seconds(30), - code: lambda.Code.fromDockerBuild(__dirname, { + code: resolveLambdaCode(userCode, __dirname, { file: "runtime/Dockerfile", buildArgs: { PYTHON_VERSION: "3.12", @@ -136,7 +143,7 @@ export class StacIngestor extends Construct { environment: { DB_SECRET_ARN: props.dbSecret.secretArn, ...props.env }, role: this.handlerRole, // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + ...otherLambdaOptions, }); // Allow handler to read DB secret @@ -167,6 +174,8 @@ export class StacIngestor extends Construct { lambdaFunctionOptions?: CustomLambdaFunctionProps; pgstacVersion?: string; }): lambda.Function { + const { code: userCode, ...otherLambdaOptions } = + props.lambdaFunctionOptions || {}; const handler = new lambda.Function(this, "stac-ingestor", { // defaults runtime: lambda.Runtime.PYTHON_3_12, @@ -174,7 +183,7 @@ export class StacIngestor extends Construct { memorySize: 2048, logRetention: aws_logs.RetentionDays.ONE_WEEK, timeout: Duration.seconds(180), - code: lambda.Code.fromDockerBuild(__dirname, { + code: resolveLambdaCode(userCode, __dirname, { file: "runtime/Dockerfile", buildArgs: { PYTHON_VERSION: "3.12", @@ -187,7 +196,7 @@ export class StacIngestor extends Construct { environment: { DB_SECRET_ARN: props.dbSecret.secretArn, ...props.env }, role: this.handlerRole, // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + ...otherLambdaOptions, }); // Allow handler to read DB secret diff --git a/lib/stac-api/index.ts b/lib/stac-api/index.ts index 30b440c7..a3bc4889 100644 --- a/lib/stac-api/index.ts +++ b/lib/stac-api/index.ts @@ -12,7 +12,7 @@ import { import { Construct } from "constructs"; import * as path from "path"; import { LambdaApiGateway } from "../lambda-api-gateway"; -import { CustomLambdaFunctionProps } from "../utils"; +import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils"; export const EXTENSIONS = { QUERY: "query", @@ -69,6 +69,8 @@ export class PgStacApiLambdaRuntime extends Construct { const enabledExtensions = props.enabledExtensions || defaultExtensions; + const { code: userCode, ...otherLambdaOptions } = props.lambdaFunctionOptions || {}; + this.lambdaFunction = new lambda.Function(this, "lambda", { // defaults runtime: lambda.Runtime.PYTHON_3_12, @@ -76,10 +78,14 @@ export class PgStacApiLambdaRuntime extends Construct { memorySize: 8192, logRetention: aws_logs.RetentionDays.ONE_WEEK, timeout: Duration.seconds(30), - code: lambda.Code.fromDockerBuild(path.join(__dirname, ".."), { - file: "stac-api/runtime/Dockerfile", - buildArgs: { PYTHON_VERSION: "3.12" }, - }), + code: resolveLambdaCode( + userCode, + path.join(__dirname, ".."), + { + file: "stac-api/runtime/Dockerfile", + buildArgs: { PYTHON_VERSION: "3.12" }, + } + ), vpc: props.vpc, vpcSubnets: props.subnetSelection, allowPublicSubnet: true, @@ -90,8 +96,8 @@ export class PgStacApiLambdaRuntime extends Construct { ENABLED_EXTENSIONS: enabledExtensions.join(","), ...props.apiEnv, }, - // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + // overwrites defaults with user-provided configurable properties (excluding code) + ...otherLambdaOptions, }); props.dbSecret.grantRead(this.lambdaFunction); diff --git a/lib/stac-auth-proxy/index.ts b/lib/stac-auth-proxy/index.ts index c73cb9e9..ab8898d0 100644 --- a/lib/stac-auth-proxy/index.ts +++ b/lib/stac-auth-proxy/index.ts @@ -4,7 +4,7 @@ import * as lambda from "aws-cdk-lib/aws-lambda"; import * as apigatewayv2 from "aws-cdk-lib/aws-apigatewayv2"; import { Construct } from "constructs"; import { LambdaApiGateway } from "../lambda-api-gateway"; -import { CustomLambdaFunctionProps } from "../utils"; +import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils"; import * as path from "path"; export class StacAuthProxyLambdaRuntime extends Construct { @@ -17,13 +17,16 @@ export class StacAuthProxyLambdaRuntime extends Construct { ) { super(scope, id); + const { code: userCode, ...otherLambdaOptions } = + props.lambdaFunctionOptions || {}; + this.lambdaFunction = new lambda.Function(this, "lambda", { runtime: lambda.Runtime.PYTHON_3_13, handler: "handler.handler", memorySize: 8192, logRetention: cdk.aws_logs.RetentionDays.ONE_WEEK, timeout: cdk.Duration.seconds(30), - code: lambda.Code.fromDockerBuild(path.join(__dirname, ".."), { + code: resolveLambdaCode(userCode, path.join(__dirname, ".."), { file: "stac-auth-proxy/runtime/Dockerfile", buildArgs: { PYTHON_VERSION: "3.13" }, }), @@ -47,7 +50,7 @@ export class StacAuthProxyLambdaRuntime extends Construct { ...props.apiEnv, }, // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + ...otherLambdaOptions, }); } } diff --git a/lib/stac-loader/index.ts b/lib/stac-loader/index.ts index 2f472785..a84feb42 100644 --- a/lib/stac-loader/index.ts +++ b/lib/stac-loader/index.ts @@ -13,7 +13,7 @@ import { import { Construct } from "constructs"; import * as path from "path"; import { PgStacDatabase } from "../database"; -import { CustomLambdaFunctionProps } from "../utils"; +import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils"; /** * Configuration properties for the StacLoader construct. @@ -412,12 +412,15 @@ export class StacLoader extends Construct { ); // Create the lambda function + const { code: userCode, ...otherLambdaOptions } = + props.lambdaFunctionOptions || {}; + this.lambdaFunction = new lambda.Function(this, "Function", { runtime: lambdaRuntime, handler: "stac_loader.handler.handler", vpc: props.vpc, vpcSubnets: props.subnetSelection, - code: lambda.Code.fromDockerBuild(path.join(__dirname, ".."), { + code: resolveLambdaCode(userCode, path.join(__dirname, ".."), { file: "stac-loader/runtime/Dockerfile", platform: "linux/amd64", buildArgs: { @@ -434,7 +437,7 @@ export class StacLoader extends Construct { ...props.environment, }, // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + ...otherLambdaOptions, }); // Grant permissions to read the database secret diff --git a/lib/tipg-api/index.ts b/lib/tipg-api/index.ts index 442fdec2..b48272b6 100644 --- a/lib/tipg-api/index.ts +++ b/lib/tipg-api/index.ts @@ -12,7 +12,7 @@ import { import { Construct } from "constructs"; import * as path from "path"; import { LambdaApiGateway } from "../lambda-api-gateway"; -import { CustomLambdaFunctionProps } from "../utils"; +import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils"; export class TiPgApiLambdaRuntime extends Construct { public readonly lambdaFunction: lambda.Function; @@ -20,6 +20,8 @@ export class TiPgApiLambdaRuntime extends Construct { constructor(scope: Construct, id: string, props: TiPgApiLambdaRuntimeProps) { super(scope, id); + const { code: userCode, ...otherLambdaOptions } = props.lambdaFunctionOptions || {}; + this.lambdaFunction = new lambda.Function(this, "lambda", { // defaults runtime: lambda.Runtime.PYTHON_3_12, @@ -27,10 +29,14 @@ export class TiPgApiLambdaRuntime extends Construct { memorySize: 1024, logRetention: logs.RetentionDays.ONE_WEEK, timeout: Duration.seconds(30), - code: lambda.Code.fromDockerBuild(path.join(__dirname, ".."), { - file: "tipg-api/runtime/Dockerfile", - buildArgs: { PYTHON_VERSION: "3.12" }, - }), + code: resolveLambdaCode( + userCode, + path.join(__dirname, ".."), + { + file: "tipg-api/runtime/Dockerfile", + buildArgs: { PYTHON_VERSION: "3.12" }, + } + ), vpc: props.vpc, vpcSubnets: props.subnetSelection, allowPublicSubnet: true, @@ -40,8 +46,8 @@ export class TiPgApiLambdaRuntime extends Construct { DB_MAX_CONN_SIZE: "1", ...props.apiEnv, }, - // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + // overwrites defaults with user-provided configurable properties (excluding code) + ...otherLambdaOptions, }); props.dbSecret.grantRead(this.lambdaFunction); diff --git a/lib/titiler-pgstac-api/index.ts b/lib/titiler-pgstac-api/index.ts index 7f3e5af8..90cfba11 100644 --- a/lib/titiler-pgstac-api/index.ts +++ b/lib/titiler-pgstac-api/index.ts @@ -13,7 +13,7 @@ import { import { Construct } from "constructs"; import * as path from "path"; import { LambdaApiGateway } from "../lambda-api-gateway"; -import { CustomLambdaFunctionProps } from "../utils"; +import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils"; // default settings that can be overridden by the user-provided environment. let defaultTitilerPgstacEnv: Record = { @@ -41,6 +41,8 @@ export class TitilerPgstacApiLambdaRuntime extends Construct { ) { super(scope, id); + const { code: userCode, ...otherLambdaOptions } = props.lambdaFunctionOptions || {}; + this.lambdaFunction = new lambda.Function(this, "lambda", { // defaults runtime: lambda.Runtime.PYTHON_3_12, @@ -48,10 +50,14 @@ export class TitilerPgstacApiLambdaRuntime extends Construct { memorySize: 3008, logRetention: aws_logs.RetentionDays.ONE_WEEK, timeout: Duration.seconds(30), - code: lambda.Code.fromDockerBuild(path.join(__dirname, ".."), { - file: "titiler-pgstac-api/runtime/Dockerfile", - buildArgs: { PYTHON_VERSION: "3.12" }, - }), + code: resolveLambdaCode( + userCode, + path.join(__dirname, ".."), + { + file: "titiler-pgstac-api/runtime/Dockerfile", + buildArgs: { PYTHON_VERSION: "3.12" }, + } + ), vpc: props.vpc, vpcSubnets: props.subnetSelection, allowPublicSubnet: true, @@ -60,8 +66,8 @@ export class TitilerPgstacApiLambdaRuntime extends Construct { ...props.apiEnv, // if user provided environment variables, merge them with the defaults. PGSTAC_SECRET_ARN: props.dbSecret.secretArn, }, - // overwrites defaults with user-provided configurable properties - ...props.lambdaFunctionOptions, + // overwrites defaults with user-provided configurable properties (excluding code) + ...otherLambdaOptions, }); // grant access to buckets using addToRolePolicy diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 8306492a..33758875 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -2,3 +2,30 @@ import { aws_lambda as lambda } from "aws-cdk-lib"; export type CustomLambdaFunctionProps = lambda.FunctionProps | any; export const DEFAULT_PGSTAC_VERSION = "0.9.5"; + +/** + * Resolves Lambda code by using custom user code if provided, + * otherwise builds a Docker image with the specified arguments. + * + * @param userCode - User-provided custom code (optional) + * @param dockerBuildPath - Path for Docker build + * @param dockerBuildOptions - Options for Docker build + * @returns Lambda code configuration + */ +export function resolveLambdaCode( + userCode?: lambda.Code, + dockerBuildPath?: string, + dockerBuildOptions?: lambda.DockerBuildAssetOptions +): lambda.Code { + if (userCode) { + return userCode; + } + + if (!dockerBuildPath || !dockerBuildOptions) { + throw new Error( + "dockerBuildPath and dockerBuildOptions are required when no custom code is provided" + ); + } + + return lambda.Code.fromDockerBuild(dockerBuildPath, dockerBuildOptions); +}