Skip to content

Commit 06280c7

Browse files
committed
feat: tipg-api
1 parent 9ea2c18 commit 06280c7

File tree

6 files changed

+203
-1
lines changed

6 files changed

+203
-1
lines changed

lib/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from "./bootstrapper";
33
export * from "./database";
44
export * from "./ingestor-api";
55
export * from "./stac-api";
6-
export * from "./titiler-pgstac-api";
6+
export * from "./titiler-pgstac-api";
7+
export * from "./tipg-api";

lib/tipg-api/index.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {
2+
Stack,
3+
aws_iam as iam,
4+
aws_ec2 as ec2,
5+
aws_rds as rds,
6+
aws_lambda as lambda,
7+
aws_secretsmanager as secretsmanager,
8+
CfnOutput,
9+
Duration,
10+
aws_logs,
11+
} from "aws-cdk-lib";
12+
import {
13+
PythonFunction,
14+
PythonFunctionProps,
15+
} from "@aws-cdk/aws-lambda-python-alpha";
16+
import { HttpApi } from "@aws-cdk/aws-apigatewayv2-alpha";
17+
import { HttpLambdaIntegration } from "@aws-cdk/aws-apigatewayv2-integrations-alpha";
18+
import { Construct } from "constructs";
19+
20+
export class TiPgApiLambda extends Construct {
21+
readonly url: string;
22+
public tiPgLambdaFunction: PythonFunction;
23+
24+
constructor(scope: Construct, id: string, props: TiPgApiLambdaProps) {
25+
super(scope, id);
26+
27+
const apiCode = props.apiCode || {
28+
entry: `${__dirname}/runtime`,
29+
index: "src/handler.py",
30+
handler: "handler",
31+
};
32+
33+
this.tiPgLambdaFunction = new PythonFunction(this, "tipg-api", {
34+
...apiCode,
35+
runtime: lambda.Runtime.PYTHON_3_10,
36+
architecture: lambda.Architecture.X86_64,
37+
environment: {
38+
PGSTAC_SECRET_ARN: props.dbSecret.secretArn,
39+
DB_MIN_CONN_SIZE: "1",
40+
DB_MAX_CONN_SIZE: "1",
41+
...props.apiEnv,
42+
},
43+
vpc: props.vpc,
44+
vpcSubnets: props.subnetSelection,
45+
allowPublicSubnet: true,
46+
memorySize: 1024,
47+
timeout: Duration.seconds(30),
48+
});
49+
50+
props.dbSecret.grantRead(this.tiPgLambdaFunction);
51+
this.tiPgLambdaFunction.connections.allowTo(props.db, ec2.Port.tcp(5432), "allow connections from tipg");
52+
53+
const tipgApi = new HttpApi(this, `${Stack.of(this).stackName}-tipg-api`, {
54+
defaultIntegration: new HttpLambdaIntegration("integration", this.tiPgLambdaFunction),
55+
});
56+
57+
this.url = tipgApi.url!;
58+
59+
new CfnOutput(this, "tipg-api-output", {
60+
exportName: `${Stack.of(this).stackName}-tip-url`,
61+
value: this.url,
62+
});
63+
}
64+
}
65+
66+
export interface TiPgApiLambdaProps {
67+
68+
/**
69+
* VPC into which the lambda should be deployed.
70+
*/
71+
readonly vpc: ec2.IVpc;
72+
73+
/**
74+
* RDS Instance with installed pgSTAC.
75+
*/
76+
readonly db: rds.IDatabaseInstance;
77+
78+
/**
79+
* Subnet into which the lambda should be deployed.
80+
*/
81+
readonly subnetSelection: ec2.SubnetSelection;
82+
83+
/**
84+
* Secret containing connection information for pgSTAC database.
85+
*/
86+
readonly dbSecret: secretsmanager.ISecret;
87+
88+
/**
89+
* Custom code to run for fastapi-pgstac.
90+
*
91+
* @default - simplified version of fastapi-pgstac
92+
*/
93+
readonly apiCode?: TiPgApiEntrypoint;
94+
95+
/**
96+
* Customized environment variables to send to titiler-pgstac runtime.
97+
*/
98+
readonly apiEnv?: Record<string, string>;
99+
}
100+
101+
export interface TiPgApiEntrypoint {
102+
/**
103+
* Path to the source of the function or the location for dependencies.
104+
*/
105+
readonly entry: PythonFunctionProps["entry"];
106+
/**
107+
* The path (relative to entry) to the index file containing the exported handler.
108+
*/
109+
readonly index: PythonFunctionProps["index"];
110+
/**
111+
* The name of the exported handler in the index file.
112+
*/
113+
readonly handler: PythonFunctionProps["handler"];
114+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tipg==0.3.1
2+
mangum==0.15.1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""tipg lambda."""
2+
3+
__version__ = "0.1.0"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Handler for AWS Lambda.
3+
"""
4+
5+
import asyncio
6+
import os
7+
from mangum import Mangum
8+
from utils import get_secret_dict
9+
from tipg.main import app
10+
from tipg.database import connect_to_db
11+
from tipg.settings import (
12+
CustomSQLSettings,
13+
DatabaseSettings,
14+
PostgresSettings,
15+
)
16+
pgstac_secret_arn = os.environ["PGSTAC_SECRET_ARN"]
17+
secret = get_secret_dict(pgstac_secret_arn)
18+
19+
postgres_settings = PostgresSettings(
20+
postgres_user=secret["username"],
21+
postgres_pass=secret["password"],
22+
postgres_host=secret["host"],
23+
postgres_port=secret["port"],
24+
postgres_dbname=secret["dbname"],
25+
)
26+
db_settings = DatabaseSettings()
27+
custom_sql_settings = CustomSQLSettings()
28+
29+
30+
@app.on_event("startup")
31+
async def startup_event() -> None:
32+
"""Connect to database on startup."""
33+
await connect_to_db(
34+
app,
35+
settings=postgres_settings,
36+
schemas=db_settings.schemas,
37+
user_sql_files=custom_sql_settings.sql_files,
38+
)
39+
await register_collection_catalog(
40+
app,
41+
schemas=db_settings.schemas,
42+
tables=db_settings.tables,
43+
exclude_tables=db_settings.exclude_tables,
44+
exclude_table_schemas=db_settings.exclude_table_schemas,
45+
functions=db_settings.functions,
46+
exclude_functions=db_settings.exclude_functions,
47+
exclude_function_schemas=db_settings.exclude_function_schemas,
48+
spatial=db_settings.only_spatial_tables,
49+
)
50+
51+
52+
handler = Mangum(app, lifespan="off")
53+
54+
if "AWS_EXECUTION_ENV" in os.environ:
55+
loop = asyncio.get_event_loop()
56+
loop.run_until_complete(app.router.startup())

lib/tipg-api/runtime/src/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import base64
2+
import json
3+
import boto3
4+
5+
6+
def get_secret_dict(secret_name: str):
7+
"""Retrieve secrets from AWS Secrets Manager
8+
9+
Args:
10+
secret_name (str): name of aws secrets manager secret containing database connection secrets
11+
profile_name (str, optional): optional name of aws profile for use in debugger only
12+
13+
Returns:
14+
secrets (dict): decrypted secrets in dict
15+
"""
16+
17+
# Create a Secrets Manager client
18+
session = boto3.session.Session()
19+
client = session.client(service_name="secretsmanager")
20+
21+
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
22+
23+
if "SecretString" in get_secret_value_response:
24+
return json.loads(get_secret_value_response["SecretString"])
25+
else:
26+
return json.loads(base64.b64decode(get_secret_value_response["SecretBinary"]))

0 commit comments

Comments
 (0)