Skip to content

Commit 24faa85

Browse files
feat: tipg-api (#62)
* feat: tipg-api * update readme * load pgstac vars into env directly in utils, fix an import bug * add CDN for tipg api --------- Co-authored-by: emileten <[email protected]>
1 parent daec1e0 commit 24faa85

File tree

8 files changed

+229
-2
lines changed

8 files changed

+229
-2
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ A STAC API implementation using [stac-fastapi](https://github.com/stac-utils/sta
1717
### [pgSTAC Titiler API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
1818
A complete dynamic tiling API using [titiler-pgstac](https://github.com/stac-utils/titiler-pgstac) to create dynamic mosaics of assets based on [STAC Search queries](https://github.com/radiantearth/stac-api-spec/tree/master/item-search). Packaged as a complete runtime for deployment with API Gateway and Lambda and fully integrated with the pgSTAC Database construct.
1919

20+
### [OGC Features/Tiles API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
21+
A complete OGC Features/Tiles API using [tipg](https://github.com/developmentseed/tipg). Packaged as a complete runtime for deployment with API Gateway and Lambda. By default the API will be connected to the Database's `public` schema.
22+
2023
### [STAC Ingestor](https://developmentseed.org/eoapi-cdk/#stacingestor-)
2124
An API for large scale STAC data ingestion and validation into a pgSTAC instance.
2225

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

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import base64
2+
import json
3+
import boto3
4+
import os
5+
6+
7+
def load_pgstac_secret(secret_name: str):
8+
"""Retrieve secrets from AWS Secrets Manager
9+
10+
Args:
11+
secret_name (str): name of aws secrets manager secret containing database connection secrets
12+
profile_name (str, optional): optional name of aws profile for use in debugger only
13+
14+
Returns:
15+
secrets (dict): decrypted secrets in dict
16+
"""
17+
18+
# Create a Secrets Manager client
19+
session = boto3.session.Session()
20+
client = session.client(service_name="secretsmanager")
21+
22+
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
23+
24+
if "SecretString" in get_secret_value_response:
25+
secret = json.loads(get_secret_value_response["SecretString"])
26+
else:
27+
secret = json.loads(base64.b64decode(get_secret_value_response["SecretBinary"]))
28+
29+
try:
30+
os.environ.update(
31+
{
32+
"postgres_host": secret["host"],
33+
"postgres_dbname": secret["dbname"],
34+
"postgres_user": secret["username"],
35+
"postgres_pass": secret["password"],
36+
"postgres_port": str(secret["port"]),
37+
}
38+
)
39+
except Exception as ex:
40+
print("Could not load the pgstac environment variables from the secret")
41+
raise ex

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ commands =
1212
pip install -r ./lib/ingestor-api/runtime/dev_requirements.txt
1313
flake8
1414
black lib --diff
15-
isort lib
15+
isort lib
1616
python -m pytest -s
1717

1818

0 commit comments

Comments
 (0)