Skip to content

Commit

Permalink
feat: tipg-api
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentsarago committed Aug 4, 2023
1 parent 9ea2c18 commit acf913d
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 2 deletions.
3 changes: 2 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from "./bootstrapper";
export * from "./database";
export * from "./ingestor-api";
export * from "./stac-api";
export * from "./titiler-pgstac-api";
export * from "./titiler-pgstac-api";
export * from "./tipg-api";
112 changes: 112 additions & 0 deletions lib/tipg-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
Stack,
aws_ec2 as ec2,
aws_rds as rds,
aws_lambda as lambda,
aws_secretsmanager as secretsmanager,
CfnOutput,
Duration,
} from "aws-cdk-lib";
import {
PythonFunction,
PythonFunctionProps,
} from "@aws-cdk/aws-lambda-python-alpha";
import { HttpApi } from "@aws-cdk/aws-apigatewayv2-alpha";
import { HttpLambdaIntegration } from "@aws-cdk/aws-apigatewayv2-integrations-alpha";
import { Construct } from "constructs";

export class TiPgApiLambda extends Construct {
readonly url: string;
public tiPgLambdaFunction: PythonFunction;

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

const apiCode = props.apiCode || {
entry: `${__dirname}/runtime`,
index: "src/handler.py",
handler: "handler",
};

this.tiPgLambdaFunction = new PythonFunction(this, "tipg-api", {
...apiCode,
runtime: lambda.Runtime.PYTHON_3_10,
architecture: lambda.Architecture.X86_64,
environment: {
PGSTAC_SECRET_ARN: props.dbSecret.secretArn,
DB_MIN_CONN_SIZE: "1",
DB_MAX_CONN_SIZE: "1",
...props.apiEnv,
},
vpc: props.vpc,
vpcSubnets: props.subnetSelection,
allowPublicSubnet: true,
memorySize: 1024,
timeout: Duration.seconds(30),
});

props.dbSecret.grantRead(this.tiPgLambdaFunction);
this.tiPgLambdaFunction.connections.allowTo(props.db, ec2.Port.tcp(5432), "allow connections from tipg");

const tipgApi = new HttpApi(this, `${Stack.of(this).stackName}-tipg-api`, {
defaultIntegration: new HttpLambdaIntegration("integration", this.tiPgLambdaFunction),
});

this.url = tipgApi.url!;

new CfnOutput(this, "tipg-api-output", {
exportName: `${Stack.of(this).stackName}-tip-url`,
value: this.url,
});
}
}

export interface TiPgApiLambdaProps {

/**
* VPC into which the lambda should be deployed.
*/
readonly vpc: ec2.IVpc;

/**
* RDS Instance with installed pgSTAC.
*/
readonly db: rds.IDatabaseInstance;

/**
* Subnet into which the lambda should be deployed.
*/
readonly subnetSelection: ec2.SubnetSelection;

/**
* Secret containing connection information for pgSTAC database.
*/
readonly dbSecret: secretsmanager.ISecret;

/**
* Custom code to run for fastapi-pgstac.
*
* @default - simplified version of fastapi-pgstac
*/
readonly apiCode?: TiPgApiEntrypoint;

/**
* Customized environment variables to send to titiler-pgstac runtime.
*/
readonly apiEnv?: Record<string, string>;
}

export interface TiPgApiEntrypoint {
/**
* Path to the source of the function or the location for dependencies.
*/
readonly entry: PythonFunctionProps["entry"];
/**
* The path (relative to entry) to the index file containing the exported handler.
*/
readonly index: PythonFunctionProps["index"];
/**
* The name of the exported handler in the index file.
*/
readonly handler: PythonFunctionProps["handler"];
}
2 changes: 2 additions & 0 deletions lib/tipg-api/runtime/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tipg==0.3.1
mangum==0.15.1
3 changes: 3 additions & 0 deletions lib/tipg-api/runtime/src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""tipg lambda."""

__version__ = "0.1.0"
58 changes: 58 additions & 0 deletions lib/tipg-api/runtime/src/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Handler for AWS Lambda.
"""

import asyncio
import os
from mangum import Mangum
from utils import get_secret_dict
from tipg.main import app
from tipg.collections import register_collection_catalog
from tipg.database import connect_to_db
from tipg.settings import (
CustomSQLSettings,
DatabaseSettings,
PostgresSettings,
)

pgstac_secret_arn = os.environ["PGSTAC_SECRET_ARN"]
secret = get_secret_dict(pgstac_secret_arn)

postgres_settings = PostgresSettings(
postgres_user=secret["username"],
postgres_pass=secret["password"],
postgres_host=secret["host"],
postgres_port=secret["port"],
postgres_dbname=secret["dbname"],
)
db_settings = DatabaseSettings()
custom_sql_settings = CustomSQLSettings()


@app.on_event("startup")
async def startup_event() -> None:
"""Connect to database on startup."""
await connect_to_db(
app,
settings=postgres_settings,
schemas=db_settings.schemas,
user_sql_files=custom_sql_settings.sql_files,
)
await register_collection_catalog(
app,
schemas=db_settings.schemas,
tables=db_settings.tables,
exclude_tables=db_settings.exclude_tables,
exclude_table_schemas=db_settings.exclude_table_schemas,
functions=db_settings.functions,
exclude_functions=db_settings.exclude_functions,
exclude_function_schemas=db_settings.exclude_function_schemas,
spatial=db_settings.only_spatial_tables,
)


handler = Mangum(app, lifespan="off")

if "AWS_EXECUTION_ENV" in os.environ:
loop = asyncio.get_event_loop()
loop.run_until_complete(app.router.startup())
26 changes: 26 additions & 0 deletions lib/tipg-api/runtime/src/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import base64
import json
import boto3


def get_secret_dict(secret_name: str):
"""Retrieve secrets from AWS Secrets Manager
Args:
secret_name (str): name of aws secrets manager secret containing database connection secrets
profile_name (str, optional): optional name of aws profile for use in debugger only
Returns:
secrets (dict): decrypted secrets in dict
"""

# Create a Secrets Manager client
session = boto3.session.Session()
client = session.client(service_name="secretsmanager")

get_secret_value_response = client.get_secret_value(SecretId=secret_name)

if "SecretString" in get_secret_value_response:
return json.loads(get_secret_value_response["SecretString"])
else:
return json.loads(base64.b64decode(get_secret_value_response["SecretBinary"]))
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ commands =
pip install -r ./lib/ingestor-api/runtime/dev_requirements.txt
flake8
black lib --diff
isort lib
isort lib
python -m pytest -s


Expand Down

0 comments on commit acf913d

Please sign in to comment.