Skip to content

Commit

Permalink
feat: tipg-api (#62)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
vincentsarago and emileten authored Aug 30, 2023
1 parent daec1e0 commit 24faa85
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 2 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ A STAC API implementation using [stac-fastapi](https://github.com/stac-utils/sta
### [pgSTAC Titiler API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
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.

### [OGC Features/Tiles API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
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.

### [STAC Ingestor](https://developmentseed.org/eoapi-cdk/#stacingestor-)
An API for large scale STAC data ingestion and validation into a pgSTAC instance.

Expand Down
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";
123 changes: 123 additions & 0 deletions lib/tipg-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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 { IDomainName, 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`, {
defaultDomainMapping: props.tipgApiDomainName ? {
domainName: props.tipgApiDomainName
} : undefined,
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>;

/**
* Custom Domain Name for tipg API. If defined, will create the
* domain name and integrate it with the tipg API.
*
* @default - undefined
*/
readonly tipgApiDomainName?: IDomainName;
}

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"
54 changes: 54 additions & 0 deletions lib/tipg-api/runtime/src/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Handler for AWS Lambda.
"""

import asyncio
import os
from mangum import Mangum
from src.utils import load_pgstac_secret

load_pgstac_secret(os.environ["PGSTAC_SECRET_ARN"]) # required for the below imports

# skipping linting rule that wants all imports at the top
from tipg.main import app # noqa: E402
from tipg.collections import register_collection_catalog # noqa: E402
from tipg.database import connect_to_db # noqa: E402
from tipg.settings import ( # noqa: E402
CustomSQLSettings, # noqa: E402
DatabaseSettings, # noqa: E402
PostgresSettings, # noqa: E402
) # noqa: E402


postgres_settings = PostgresSettings()
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())
41 changes: 41 additions & 0 deletions lib/tipg-api/runtime/src/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import base64
import json
import boto3
import os


def load_pgstac_secret(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:
secret = json.loads(get_secret_value_response["SecretString"])
else:
secret = json.loads(base64.b64decode(get_secret_value_response["SecretBinary"]))

try:
os.environ.update(
{
"postgres_host": secret["host"],
"postgres_dbname": secret["dbname"],
"postgres_user": secret["username"],
"postgres_pass": secret["password"],
"postgres_port": str(secret["port"]),
}
)
except Exception as ex:
print("Could not load the pgstac environment variables from the secret")
raise ex
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 24faa85

Please sign in to comment.