Skip to content

Commit 6b141e9

Browse files
authored
fix: add pgbouncer secretBootstrapper as a dependency for lambda functions (#221)
1 parent 03472ba commit 6b141e9

File tree

4 files changed

+140
-15
lines changed

4 files changed

+140
-15
lines changed

lib/stac-api/index.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {
1212
import { Construct } from "constructs";
1313
import * as path from "path";
1414
import { LambdaApiGateway } from "../lambda-api-gateway";
15-
import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils";
15+
import {
16+
CustomLambdaFunctionProps,
17+
resolveLambdaCode,
18+
extractDatabaseDependencies,
19+
createLambdaVersionWithDependencies,
20+
} from "../utils";
1621

1722
export const EXTENSIONS = {
1823
QUERY: "query",
@@ -204,10 +209,23 @@ export class PgStacApiLambda extends Construct {
204209
});
205210
this.stacApiLambdaFunction = this.lambdaFunction = runtime.lambdaFunction;
206211

212+
// Determine which lambda to use for API Gateway
213+
let apiLambda: lambda.Function | lambda.Version;
214+
if (props.enableSnapStart) {
215+
// Extract dependencies from database if it's a PgStacDatabase with PgBouncer
216+
const dbDependencies = extractDatabaseDependencies(props.db);
217+
218+
// Create version with dependencies to ensure snapshot creation waits
219+
apiLambda = createLambdaVersionWithDependencies(
220+
runtime.lambdaFunction,
221+
dbDependencies,
222+
);
223+
} else {
224+
apiLambda = runtime.lambdaFunction;
225+
}
226+
207227
const { api } = new LambdaApiGateway(this, "stac-api", {
208-
lambdaFunction: props.enableSnapStart!
209-
? runtime.lambdaFunction.currentVersion
210-
: runtime.lambdaFunction,
228+
lambdaFunction: apiLambda,
211229
domainName: props.domainName ?? props.stacApiDomainName,
212230
});
213231

lib/tipg-api/index.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {
1212
import { Construct } from "constructs";
1313
import * as path from "path";
1414
import { LambdaApiGateway } from "../lambda-api-gateway";
15-
import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils";
15+
import {
16+
CustomLambdaFunctionProps,
17+
resolveLambdaCode,
18+
extractDatabaseDependencies,
19+
createLambdaVersionWithDependencies,
20+
} from "../utils";
1621

1722
export class TiPgApiLambdaRuntime extends Construct {
1823
public readonly lambdaFunction: lambda.Function;
@@ -146,10 +151,23 @@ export class TiPgApiLambda extends Construct {
146151
});
147152
this.tiPgLambdaFunction = this.lambdaFunction = runtime.lambdaFunction;
148153

154+
// Determine which lambda to use for API Gateway
155+
let apiLambda: lambda.Function | lambda.Version;
156+
if (props.enableSnapStart) {
157+
// Extract dependencies from database if it's a PgStacDatabase with PgBouncer
158+
const dbDependencies = extractDatabaseDependencies(props.db);
159+
160+
// Create version with dependencies to ensure snapshot creation waits
161+
apiLambda = createLambdaVersionWithDependencies(
162+
runtime.lambdaFunction,
163+
dbDependencies,
164+
);
165+
} else {
166+
apiLambda = runtime.lambdaFunction;
167+
}
168+
149169
const { api } = new LambdaApiGateway(this, "api", {
150-
lambdaFunction: props.enableSnapStart!
151-
? runtime.lambdaFunction.currentVersion
152-
: runtime.lambdaFunction,
170+
lambdaFunction: apiLambda,
153171
domainName: props.domainName ?? props.tipgApiDomainName,
154172
});
155173

lib/titiler-pgstac-api/index.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import {
1313
import { Construct } from "constructs";
1414
import * as path from "path";
1515
import { LambdaApiGateway } from "../lambda-api-gateway";
16-
import { CustomLambdaFunctionProps, resolveLambdaCode } from "../utils";
16+
import {
17+
CustomLambdaFunctionProps,
18+
resolveLambdaCode,
19+
extractDatabaseDependencies,
20+
createLambdaVersionWithDependencies,
21+
} from "../utils";
1722

1823
// default settings that can be overridden by the user-provided environment.
1924
let defaultTitilerPgstacEnv: Record<string, string> = {
@@ -190,10 +195,23 @@ export class TitilerPgstacApiLambda extends Construct {
190195
this.titilerPgstacLambdaFunction = this.lambdaFunction =
191196
runtime.lambdaFunction;
192197

198+
// Determine which lambda to use for API Gateway
199+
let apiLambda: lambda.Function | lambda.Version;
200+
if (props.enableSnapStart) {
201+
// Extract dependencies from database if it's a PgStacDatabase with PgBouncer
202+
const dbDependencies = extractDatabaseDependencies(props.db);
203+
204+
// Create version with dependencies to ensure snapshot creation waits
205+
apiLambda = createLambdaVersionWithDependencies(
206+
runtime.lambdaFunction,
207+
dbDependencies,
208+
);
209+
} else {
210+
apiLambda = runtime.lambdaFunction;
211+
}
212+
193213
const { api } = new LambdaApiGateway(this, "titlier-pgstac-api", {
194-
lambdaFunction: props.enableSnapStart
195-
? runtime.lambdaFunction.currentVersion
196-
: runtime.lambdaFunction,
214+
lambdaFunction: apiLambda,
197215
domainName: props.domainName ?? props.titilerPgstacApiDomainName,
198216
});
199217

lib/utils/index.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { aws_lambda as lambda } from "aws-cdk-lib";
1+
import { aws_lambda as lambda, CustomResource } from "aws-cdk-lib";
22

33
export type CustomLambdaFunctionProps = lambda.FunctionProps | any;
44
export const DEFAULT_PGSTAC_VERSION = "0.9.5";
@@ -15,17 +15,88 @@ export const DEFAULT_PGSTAC_VERSION = "0.9.5";
1515
export function resolveLambdaCode(
1616
userCode?: lambda.Code,
1717
dockerBuildPath?: string,
18-
dockerBuildOptions?: lambda.DockerBuildAssetOptions
18+
dockerBuildOptions?: lambda.DockerBuildAssetOptions,
1919
): lambda.Code {
2020
if (userCode) {
2121
return userCode;
2222
}
2323

2424
if (!dockerBuildPath || !dockerBuildOptions) {
2525
throw new Error(
26-
"dockerBuildPath and dockerBuildOptions are required when no custom code is provided"
26+
"dockerBuildPath and dockerBuildOptions are required when no custom code is provided",
2727
);
2828
}
2929

3030
return lambda.Code.fromDockerBuild(dockerBuildPath, dockerBuildOptions);
3131
}
32+
33+
/**
34+
* Type guard to check if a value has the secretBootstrapper property.
35+
*
36+
* This is used to detect PgStacDatabase constructs with PgBouncer enabled,
37+
* which expose a secretBootstrapper CustomResource that completes after
38+
* the database secret is updated with PgBouncer connection information.
39+
*
40+
* @param value - Value to check
41+
* @returns true if value has a defined secretBootstrapper property
42+
*/
43+
function hasSecretBootstrapper(
44+
value: any,
45+
): value is { secretBootstrapper: CustomResource } {
46+
return (
47+
value &&
48+
typeof value === "object" &&
49+
"secretBootstrapper" in value &&
50+
value.secretBootstrapper !== undefined
51+
);
52+
}
53+
54+
/**
55+
* Extracts database dependencies from a database construct if available.
56+
*
57+
* For PgStacDatabase with PgBouncer enabled, returns the secretBootstrapper
58+
* CustomResource which ensures the database secret is fully initialized before
59+
* dependent resources are created.
60+
*
61+
* This is critical for SnapStart Lambda functions, as the snapshot must not
62+
* be created until the secret contains the correct connection information.
63+
*
64+
* @param db - Database construct (may be PgStacDatabase, IDatabaseInstance, etc.)
65+
* @returns Array of CustomResources to depend on (empty if none available)
66+
*/
67+
export function extractDatabaseDependencies(db: any): CustomResource[] {
68+
if (hasSecretBootstrapper(db)) {
69+
return [db.secretBootstrapper];
70+
}
71+
return [];
72+
}
73+
74+
/**
75+
* Creates a Lambda version with dependencies for SnapStart.
76+
*
77+
* When SnapStart is enabled, the version creation triggers snapshot creation.
78+
* Dependencies ensure the snapshot isn't created until prerequisites are met,
79+
* such as database secrets being fully initialized.
80+
*
81+
* This prevents race conditions where the SnapStart snapshot might capture
82+
* incorrect or incomplete configuration.
83+
*
84+
* @param lambdaFunction - Lambda function to create a version from
85+
* @param dependencies - Array of resources to depend on (pass empty array if none)
86+
* @returns Lambda version with dependencies applied
87+
*/
88+
export function createLambdaVersionWithDependencies(
89+
lambdaFunction: lambda.Function,
90+
dependencies: CustomResource[],
91+
): lambda.Version {
92+
const version = lambdaFunction.currentVersion;
93+
94+
// Add dependencies to the version resource to ensure proper ordering
95+
if (dependencies && dependencies.length > 0) {
96+
dependencies.forEach((dep) => {
97+
version.node.addDependency(dep);
98+
});
99+
}
100+
101+
return version;
102+
}

0 commit comments

Comments
 (0)