Skip to content

Commit 9e49e7e

Browse files
feat: update runtimes and add pgstac customization options (#100)
* feat: update runtimes and add pgstac customization options * Update lib/tipg-api/runtime/requirements.txt Co-authored-by: Emile Tenezakis <[email protected]> * change defaults * revert version updates * Revert "revert version updates" This reverts commit 0d675e2. * back to 0.7.1 * (fix): fix database bootstrap (#102) * (fix): fix database bootstrap * fix handler * update to pgstac 0.8.5 --------- Co-authored-by: Emile Tenezakis <[email protected]>
1 parent a5e398e commit 9e49e7e

File tree

7 files changed

+119
-89
lines changed

7 files changed

+119
-89
lines changed

lib/database/bootstrapper_runtime/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ RUN rm -rf /asset/asyncio*
1717

1818
# A command must be present avoid the following error on CDK deploy:
1919
# Error response from daemon: No command specified
20-
CMD [ "echo", "ready to go!" ]
20+
CMD [ "echo", "ready to go!" ]

lib/database/bootstrapper_runtime/handler.py

Lines changed: 103 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
import json
7+
import logging
78

89
import boto3
910
import httpx
@@ -13,6 +14,8 @@
1314
from pypgstac.db import PgstacDB
1415
from pypgstac.migrate import Migrate
1516

17+
logger = logging.getLogger("eoapi-bootstrap")
18+
1619

1720
def send(
1821
event,
@@ -36,10 +39,6 @@ def send(
3639
It isn't available for source code that's stored in Amazon S3 buckets.
3740
For code in buckets, you must write your own functions to send responses.
3841
"""
39-
responseUrl = event["ResponseURL"]
40-
41-
print(responseUrl)
42-
4342
responseBody = {}
4443
responseBody["Status"] = responseStatus
4544
responseBody["Reason"] = (
@@ -53,21 +52,21 @@ def send(
5352
responseBody["Data"] = responseData
5453

5554
json_responseBody = json.dumps(responseBody)
56-
57-
print("Response body:\n" + json_responseBody)
58-
59-
headers = {"content-type": "", "content-length": str(len(json_responseBody))}
55+
print("Response body:\n " + json_responseBody)
6056

6157
try:
6258
response = httpx.put(
63-
responseUrl,
59+
event["ResponseURL"],
6460
data=json_responseBody,
65-
headers=headers,
61+
headers={"content-type": "", "content-length": str(len(json_responseBody))},
6662
timeout=30,
6763
)
68-
print("Status code: " + response.status_code)
64+
print("Status code: ", response.status_code)
65+
logger.debug(f"OK - Status code: {response.status_code}")
66+
6967
except Exception as e:
7068
print("send(..) failed executing httpx.put(..): " + str(e))
69+
logger.debug(f"NOK - failed executing PUT requests: {e}")
7170

7271

7372
def get_secret(secret_name):
@@ -84,9 +83,9 @@ def create_db(cursor, db_name: str) -> None:
8483
sql.SQL("SELECT 1 FROM pg_catalog.pg_database " "WHERE datname = %s"), [db_name]
8584
)
8685
if cursor.fetchone():
87-
print(f"database {db_name} exists, not creating DB")
86+
print(f" database {db_name} exists, not creating DB")
8887
else:
89-
print(f"database {db_name} not found, creating...")
88+
print(f" database {db_name} not found, creating...")
9089
cursor.execute(
9190
sql.SQL("CREATE DATABASE {db_name}").format(db_name=sql.Identifier(db_name))
9291
)
@@ -114,8 +113,8 @@ def create_user(cursor, username: str, password: str) -> None:
114113
)
115114

116115

117-
def create_permissions(cursor, db_name: str, username: str) -> None:
118-
"""Add permissions."""
116+
def update_user_permissions(cursor, db_name: str, username: str) -> None:
117+
"""Update eoAPI user permissions."""
119118
cursor.execute(
120119
sql.SQL(
121120
"GRANT CONNECT ON DATABASE {db_name} TO {username};"
@@ -140,6 +139,33 @@ def register_extensions(cursor) -> None:
140139
cursor.execute(sql.SQL("CREATE EXTENSION IF NOT EXISTS postgis;"))
141140

142141

142+
###############################################################################
143+
# PgSTAC Customization
144+
###############################################################################
145+
def customization(cursor, params) -> None:
146+
"""
147+
CUSTOMIZED YOUR PGSTAC DATABASE
148+
149+
ref: https://github.com/stac-utils/pgstac/blob/main/docs/src/pgstac.md
150+
151+
"""
152+
if str(params.get("context", "FALSE")).upper() == "TRUE":
153+
# Add CONTEXT=ON
154+
pgstac_settings = """
155+
INSERT INTO pgstac_settings (name, value)
156+
VALUES ('context', 'on')
157+
ON CONFLICT ON CONSTRAINT pgstac_settings_pkey DO UPDATE SET value = excluded.value;"""
158+
cursor.execute(sql.SQL(pgstac_settings))
159+
160+
if str(params.get("mosaic_index", "TRUE")).upper() == "TRUE":
161+
# Create index of searches with `mosaic`` type
162+
cursor.execute(
163+
sql.SQL(
164+
"CREATE INDEX IF NOT EXISTS searches_mosaic ON searches ((true)) WHERE metadata->>'type'='mosaic';"
165+
)
166+
)
167+
168+
143169
def handler(event, context):
144170
"""Lambda Handler."""
145171
print(f"Handling {event}")
@@ -149,88 +175,90 @@ def handler(event, context):
149175

150176
try:
151177
params = event["ResourceProperties"]
152-
connection_params = get_secret(params["conn_secret_arn"])
153-
user_params = get_secret(params["new_user_secret_arn"])
154-
155-
print("Connecting to admin DB...")
156-
admin_db_conninfo = make_conninfo(
157-
dbname=connection_params.get("dbname", "postgres"),
158-
user=connection_params["username"],
159-
password=connection_params["password"],
160-
host=connection_params["host"],
161-
port=connection_params["port"],
178+
179+
# Admin (AWS RDS) user/password/dbname parameters
180+
admin_params = get_secret(params["conn_secret_arn"])
181+
182+
# Custom eoAPI user/password/dbname parameters
183+
eoapi_params = get_secret(params["new_user_secret_arn"])
184+
185+
print("Connecting to RDS...")
186+
rds_conninfo = make_conninfo(
187+
dbname=admin_params.get("dbname", "postgres"),
188+
user=admin_params["username"],
189+
password=admin_params["password"],
190+
host=admin_params["host"],
191+
port=admin_params["port"],
162192
)
163-
with psycopg.connect(admin_db_conninfo, autocommit=True) as conn:
193+
with psycopg.connect(rds_conninfo, autocommit=True) as conn:
164194
with conn.cursor() as cur:
165-
print("Creating database...")
195+
print(f"Creating eoAPI '{eoapi_params['dbname']}' database...")
166196
create_db(
167197
cursor=cur,
168-
db_name=user_params["dbname"],
198+
db_name=eoapi_params["dbname"],
169199
)
170200

171-
print("Creating user...")
201+
print(f"Creating eoAPI '{eoapi_params['username']}' user...")
172202
create_user(
173203
cursor=cur,
174-
username=user_params["username"],
175-
password=user_params["password"],
204+
username=eoapi_params["username"],
205+
password=eoapi_params["password"],
176206
)
177207

178-
# Install extensions on the user DB with
179-
# superuser permissions, since they will
180-
# otherwise fail to install when run as
181-
# the non-superuser within the pgstac
182-
# migrations.
183-
print("Connecting to STAC DB...")
184-
stac_db_conninfo = make_conninfo(
185-
dbname=user_params["dbname"],
186-
user=connection_params["username"],
187-
password=connection_params["password"],
188-
host=connection_params["host"],
189-
port=connection_params["port"],
208+
# Install postgis and pgstac on the eoapi database with
209+
# superuser permissions
210+
print(f"Connecting to eoAPI '{eoapi_params['dbname']}' database...")
211+
eoapi_db_admin_conninfo = make_conninfo(
212+
dbname=eoapi_params["dbname"],
213+
user=admin_params["username"],
214+
password=admin_params["password"],
215+
host=admin_params["host"],
216+
port=admin_params["port"],
190217
)
191-
with psycopg.connect(stac_db_conninfo, autocommit=True) as conn:
218+
with psycopg.connect(eoapi_db_admin_conninfo, autocommit=True) as conn:
192219
with conn.cursor() as cur:
193-
print("Registering PostGIS ...")
220+
print(
221+
f"Registering Extension in '{eoapi_params['dbname']}' database..."
222+
)
194223
register_extensions(cursor=cur)
195224

196-
stac_db_admin_dsn = (
197-
"postgresql://{user}:{password}@{host}:{port}/{dbname}".format(
198-
dbname=user_params.get("dbname", "postgres"),
199-
user=connection_params["username"],
200-
password=connection_params["password"],
201-
host=connection_params["host"],
202-
port=connection_params["port"],
203-
)
204-
)
205-
206-
pgdb = PgstacDB(dsn=stac_db_admin_dsn, debug=True)
207-
print(f"Current {pgdb.version=}")
225+
print("Starting PgSTAC Migration ")
226+
with PgstacDB(connection=conn, debug=True) as pgdb:
227+
print(f"Current PgSTAC Version: {pgdb.version}")
208228

209-
# As admin, run migrations
210-
print("Running migrations...")
211-
Migrate(pgdb).run_migration(params["pgstac_version"])
212-
213-
# Assign appropriate permissions to user (requires pgSTAC migrations to have run)
214-
with psycopg.connect(admin_db_conninfo, autocommit=True) as conn:
215-
with conn.cursor() as cur:
216-
print("Setting permissions...")
217-
create_permissions(
218-
cursor=cur,
219-
db_name=user_params["dbname"],
220-
username=user_params["username"],
221-
)
229+
print(f"Running migrations to PgSTAC {params['pgstac_version']}...")
230+
Migrate(pgdb).run_migration(params["pgstac_version"])
222231

223-
print("Adding mosaic index...")
224232
with psycopg.connect(
225-
stac_db_admin_dsn,
233+
eoapi_db_admin_conninfo,
226234
autocommit=True,
227235
options="-c search_path=pgstac,public -c application_name=pgstac",
228236
) as conn:
229-
conn.execute(
230-
sql.SQL(
231-
"CREATE INDEX IF NOT EXISTS searches_mosaic ON searches ((true)) WHERE metadata->>'type'='mosaic';"
237+
print("Customize PgSTAC database...")
238+
# Update permissions to eoAPI user to assume pgstac_* roles
239+
with conn.cursor() as cur:
240+
print(f"Update '{eoapi_params['username']}' permissions...")
241+
update_user_permissions(
242+
cursor=cur,
243+
db_name=eoapi_params["dbname"],
244+
username=eoapi_params["username"],
232245
)
246+
247+
customization(cursor=cur, params=params)
248+
249+
# Make sure the user can access the database
250+
eoapi_user_dsn = (
251+
"postgresql://{user}:{password}@{host}:{port}/{dbname}".format(
252+
dbname=eoapi_params["dbname"],
253+
user=eoapi_params["username"],
254+
password=eoapi_params["password"],
255+
host=admin_params["host"],
256+
port=admin_params["port"],
233257
)
258+
)
259+
print("Checking eoAPI user access to the PgSTAC database...")
260+
with PgstacDB(dsn=eoapi_user_dsn, debug=True) as pgdb:
261+
print(f" OK - User has access to pgstac db, pgstac schema version: {pgdb.version}")
234262

235263
except Exception as e:
236264
print(f"Unable to bootstrap database with exception={e}")

lib/database/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import { Construct } from "constructs";
1414
import { CustomLambdaFunctionProps } from "../utils";
1515

1616
const instanceSizes: Record<string, number> = require("./instance-memory.json");
17-
const DEFAULT_PGSTAC_VERSION = "0.7.10";
17+
const DEFAULT_PGSTAC_VERSION = "0.8.5";
18+
19+
let defaultPgSTACCustomOptions :{ [key: string]: any } = {
20+
"context": "FALSE",
21+
"mosaic_index": "TRUE"
22+
}
1823

1924
function hasVpc(
2025
instance: rds.DatabaseInstance | rds.IDatabaseInstance
@@ -106,12 +111,7 @@ export class PgStacDatabase extends Construct {
106111
// connect to database
107112
this.db.connections.allowFrom(handler, ec2.Port.tcp(5432));
108113

109-
let customResourceProperties : { [key: string]: any} = {};
110-
111-
// if customResourceProperties are provided, fill in the values.
112-
if (props.customResourceProperties) {
113-
Object.assign(customResourceProperties, props.customResourceProperties);
114-
}
114+
let customResourceProperties : { [key: string]: any} = props.customResourceProperties ? { ...defaultPgSTACCustomOptions, ...props.customResourceProperties } : defaultPgSTACCustomOptions;
115115

116116
// update properties
117117
customResourceProperties["conn_secret_arn"] = this.db.secret!.secretArn;
@@ -195,7 +195,7 @@ export interface PgStacDatabaseProps extends rds.DatabaseInstanceProps {
195195
/**
196196
* Lambda function Custom Resource properties. A custom resource property is going to be created
197197
* to trigger the boostrapping lambda function. This parameter allows the user to specify additional properties
198-
* on top of the defaults ones.
198+
* on top of the defaults ones.
199199
*
200200
*/
201201
readonly customResourceProperties?: {

lib/ingestor-api/runtime/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ orjson>=3.6.8
55
psycopg[binary,pool]>=3.0.15
66
pydantic_ssm_settings>=0.2.0
77
pydantic>=1.9.0
8-
pypgstac==0.7.10
8+
pypgstac==0.8.5
99
requests>=2.27.1
1010
# Waiting for https://github.com/stac-utils/stac-pydantic/pull/116
1111
stac-pydantic @ git+https://github.com/alukach/stac-pydantic.git@patch-1

lib/tipg-api/runtime/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ FROM --platform=linux/amd64 public.ecr.aws/lambda/python:${PYTHON_VERSION}
44
WORKDIR /tmp
55
RUN python -m pip install pip -U
66

7-
RUN python -m pip install tipg==0.3.1 "mangum>=0.14,<0.15" -t /asset --no-binary pydantic
7+
COPY runtime/requirements.txt requirements.txt
8+
RUN python -m pip install -r requirements.txt "mangum>=0.14,<0.15" -t /asset --no-binary pydantic
89

910
# Reduce package size and remove useless files
1011
RUN cd /asset && find . -type f -name '*.pyc' | while read f; do n=$(echo $f | sed 's/__pycache__\///' | sed 's/.cpython-[0-9]*//'); cp $f $n; done;
@@ -14,4 +15,4 @@ RUN find /asset -type d -a -name 'tests' -print0 | xargs -0 rm -rf
1415

1516
COPY runtime/src/*.py /asset/
1617

17-
CMD ["echo", "hello world"]
18+
CMD ["echo", "hello world"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tipg==0.6.3
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
titiler.pgstac==0.5.1
2-
psycopg[binary, pool]
1+
titiler.pgstac==1.2.2
2+
psycopg[binary, pool]

0 commit comments

Comments
 (0)