Skip to content

Commit

Permalink
fix(api)!: use root path for prefixing (#241)
Browse files Browse the repository at this point in the history
# What
- Use fast-api root path configuration and mangum api_base_path instead
of overwriting the api host in the http lambda integration
- Simplify custom domain management. If using cloudfront, do not create custom api-specific
subdomains--just use the default apigateway urls route based on root
path settings provided in raster and stac api configuration.
  • Loading branch information
anayeaye authored Nov 1, 2023
2 parents 7f0cbbc + 7d13aa7 commit dd00f5a
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 135 deletions.
4 changes: 2 additions & 2 deletions .example.env
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ VEDA_RASTER_EXPORT_ASSUME_ROLE_CREDS_AS_ENVS=False

VEDA_DB_PUBLICLY_ACCESSIBLE=TRUE

VEDA_RASTER_PATH_PREFIX=
VEDA_STAC_PATH_PREFIX=
VEDA_RASTER_ROOT_PATH=
VEDA_STAC_ROOT_PATH=

STAC_BROWSER_BUCKET=
STAC_URL=
Expand Down
4 changes: 2 additions & 2 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
stage=veda_app_settings.stage_name(),
vpc=vpc.vpc,
database=database,
domain_name=domain.raster_domain_name,
domain=domain,
)

stac_api = StacApiLambdaConstruct(
Expand All @@ -71,7 +71,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
vpc=vpc.vpc,
database=database,
raster_api=raster_api,
domain_name=domain.stac_domain_name,
domain=domain,
)

veda_routes = CloudfrontDistributionConstruct(
Expand Down
4 changes: 4 additions & 0 deletions domain/infrastructure/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class vedaDomainSettings(BaseSettings):
hosted_zone_name: Optional[str] = Field(
None, description="Custom domain name, i.e. veda-backend.xyz"
)
shared_subdomain: bool = Field(
False,
description="Create a subdomain for app stage to used for all endpoints, i.e. <stage>.veda-backend.com/api/raster",
)
api_prefix: Optional[str] = Field(
None,
description=(
Expand Down
131 changes: 76 additions & 55 deletions domain/infrastructure/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,63 +57,84 @@ def __init__(
),
)

# Use custom api prefix if provided or deployment stage if not
if veda_domain_settings.api_prefix:
raster_url_prefix = f"{veda_domain_settings.api_prefix.lower()}-raster"
stac_url_prefix = f"{veda_domain_settings.api_prefix.lower()}-stac"
else:
raster_url_prefix = f"{stage.lower()}-raster"
stac_url_prefix = f"{stage.lower()}-stac"
raster_domain_name = f"{raster_url_prefix}.{hosted_zone_name}"
stac_domain_name = f"{stac_url_prefix}.{hosted_zone_name}"
# Either create one subdomain to share for all endpoints or create a unique subdomain for each

self.raster_domain_name = aws_apigatewayv2_alpha.DomainName(
self,
"rasterApiCustomDomain",
domain_name=raster_domain_name,
certificate=certificate,
)

aws_route53.ARecord(
self,
"raster-api-dns-record",
zone=hosted_zone,
target=aws_route53.RecordTarget.from_alias(
aws_route53_targets.ApiGatewayv2DomainProperties(
regional_domain_name=self.raster_domain_name.regional_domain_name,
regional_hosted_zone_id=self.raster_domain_name.regional_hosted_zone_id,
if not veda_domain_settings.shared_subdomain:
# Use custom api prefix if provided or deployment stage if not
if veda_domain_settings.api_prefix:
raster_url_prefix = (
f"{veda_domain_settings.api_prefix.lower()}-raster"
)
),
# Note: CDK will append the hosted zone name (eg: `veda-backend.xyz` to this record name)
record_name=raster_url_prefix,
)
stac_url_prefix = f"{veda_domain_settings.api_prefix.lower()}-stac"
else:
raster_url_prefix = f"{stage.lower()}-raster"
stac_url_prefix = f"{stage.lower()}-stac"
raster_domain_name = f"{raster_url_prefix}.{hosted_zone_name}"
stac_domain_name = f"{stac_url_prefix}.{hosted_zone_name}"

self.stac_domain_name = aws_apigatewayv2_alpha.DomainName(
self,
"stacApiCustomDomain",
domain_name=stac_domain_name,
certificate=certificate,
)
self.raster_domain_name = aws_apigatewayv2_alpha.DomainName(
self,
"rasterApiCustomDomain",
domain_name=raster_domain_name,
certificate=certificate,
)

aws_route53.ARecord(
self,
"stac-api-dns-record",
zone=hosted_zone,
target=aws_route53.RecordTarget.from_alias(
aws_route53_targets.ApiGatewayv2DomainProperties(
regional_domain_name=self.stac_domain_name.regional_domain_name,
regional_hosted_zone_id=self.stac_domain_name.regional_hosted_zone_id,
)
),
# Note: CDK will append the hosted zone name (eg: `veda-backend.xyz` to this record name)
record_name=stac_url_prefix,
)
aws_route53.ARecord(
self,
"raster-api-dns-record",
zone=hosted_zone,
target=aws_route53.RecordTarget.from_alias(
aws_route53_targets.ApiGatewayv2DomainProperties(
regional_domain_name=self.raster_domain_name.regional_domain_name,
regional_hosted_zone_id=self.raster_domain_name.regional_hosted_zone_id,
)
),
# Note: CDK will append the hosted zone name (eg: `veda-backend.xyz` to this record name)
record_name=raster_url_prefix,
)

CfnOutput(
self,
"raster-api",
value=f"https://{raster_url_prefix}.{hosted_zone_name}/docs",
)
CfnOutput(
self, "stac-api", value=f"https://{stac_url_prefix}.{hosted_zone_name}/"
)
self.stac_domain_name = aws_apigatewayv2_alpha.DomainName(
self,
"stacApiCustomDomain",
domain_name=stac_domain_name,
certificate=certificate,
)

aws_route53.ARecord(
self,
"stac-api-dns-record",
zone=hosted_zone,
target=aws_route53.RecordTarget.from_alias(
aws_route53_targets.ApiGatewayv2DomainProperties(
regional_domain_name=self.stac_domain_name.regional_domain_name,
regional_hosted_zone_id=self.stac_domain_name.regional_hosted_zone_id,
)
),
# Note: CDK will append the hosted zone name (eg: `veda-backend.xyz` to this record name)
record_name=stac_url_prefix,
)

CfnOutput(
self,
"raster-api",
value=f"https://{raster_url_prefix}.{hosted_zone_name}/docs",
)
CfnOutput(
self,
"stac-api",
value=f"https://{stac_url_prefix}.{hosted_zone_name}/",
)

if veda_domain_settings.shared_subdomain:
self.shared_sub_domain_name = aws_apigatewayv2_alpha.DomainName(
self,
"cfSharedApiCustomDomain",
domain_name=f"{stage}.{hosted_zone_name}",
certificate=certificate,
)

CfnOutput(
self,
"shared-sub-domain-name",
value=f"https://{stage}.{hosted_zone_name}/",
)
14 changes: 2 additions & 12 deletions raster_api/infrastructure/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,9 @@ class vedaRasterSettings(BaseSettings):
description="Set optional global parameter to 'requester' if the requester agrees to pay S3 transfer costs",
)

raster_path_prefix: Optional[str] = Field(
raster_root_path: str = Field(
"",
description="Optional path prefix to add to all api endpoints",
)

domain_hosted_zone_name: Optional[str] = Field(
None,
description="Domain name for the cloudfront distribution",
)

cloudfront: Optional[bool] = Field(
False,
description="Boolean if Cloudfront Distribution should be deployed",
description="Optional root path for all api endpoints",
)

class Config:
Expand Down
29 changes: 12 additions & 17 deletions raster_api/infrastructure/construct.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""CDK Constrcut for a Lambda based TiTiler API with pgstac extension."""
import os
import typing
from typing import Optional

from aws_cdk import (
CfnOutput,
Expand All @@ -16,6 +18,9 @@

from .config import veda_raster_settings

if typing.TYPE_CHECKING:
from domain.infrastructure.construct import DomainConstruct


class RasterApiLambdaConstruct(Construct):
"""CDK Constrcut for a Lambda based TiTiler API with pgstac extension."""
Expand All @@ -28,7 +33,8 @@ def __init__(
vpc,
database,
code_dir: str = "./",
domain_name: aws_apigatewayv2_alpha.DomainName = None,
# domain_name: aws_apigatewayv2_alpha.DomainName = None,
domain: Optional["DomainConstruct"] = None,
**kwargs,
) -> None:
"""."""
Expand Down Expand Up @@ -71,7 +77,7 @@ def __init__(
)

veda_raster_function.add_environment(
"VEDA_RASTER_PATH_PREFIX", veda_raster_settings.raster_path_prefix
"VEDA_RASTER_ROOT_PATH", veda_raster_settings.raster_root_path
)

# Optional AWS S3 requester pays global setting
Expand All @@ -81,19 +87,6 @@ def __init__(
)

integration_kwargs = dict(handler=veda_raster_function)
if (
veda_raster_settings.domain_hosted_zone_name
and veda_raster_settings.cloudfront
):
integration_kwargs[
"parameter_mapping"
] = aws_apigatewayv2_alpha.ParameterMapping().overwrite_header(
"host",
aws_apigatewayv2_alpha.MappingValue(
f"{stage}.{veda_raster_settings.domain_hosted_zone_name}"
),
)

raster_api_integration = (
aws_apigatewayv2_integrations_alpha.HttpLambdaIntegration(
construct_id,
Expand All @@ -102,9 +95,11 @@ def __init__(
)

domain_mapping = None
if domain_name:
# Legacy method to use a custom subdomain for this api (i.e. <stage>-raster.<domain-name>.com)
# If using a custom root path and/or a proxy server, do not use a custom subdomain
if domain and domain.raster_domain_name:
domain_mapping = aws_apigatewayv2_alpha.DomainMappingOptions(
domain_name=domain_name
domain_name=domain.raster_domain_name
)

self.raster_api = aws_apigatewayv2_alpha.HttpApi(
Expand Down
2 changes: 1 addition & 1 deletion raster_api/runtime/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async def startup_event() -> None:
await connect_to_db(app, settings=settings.load_postgres_settings())


handler = Mangum(app, lifespan="off")
handler = Mangum(app, lifespan="off", api_gateway_base_path=app.root_path)

if "AWS_EXECUTION_ENV" in os.environ:
loop = asyncio.get_event_loop()
Expand Down
20 changes: 9 additions & 11 deletions raster_api/runtime/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ async def lifespan(app: FastAPI):
await close_db_connection(app)


path_prefix = settings.path_prefix
app = FastAPI(
title=settings.name,
version=veda_raster_version,
openapi_url=f"{path_prefix}/openapi.json",
docs_url=f"{path_prefix}/docs",
openapi_url="/openapi.json",
docs_url="/docs",
lifespan=lifespan,
root_path=settings.root_path,
)

# router to be applied to all titiler route factories (improves logs with FastAPI context)
Expand All @@ -66,7 +66,7 @@ async def lifespan(app: FastAPI):
# /mosaic - PgSTAC Mosaic titiler endpoint
###############################################################################
mosaic = MosaicTilerFactory(
router_prefix=f"{path_prefix}/mosaic",
router_prefix="/mosaic",
optional_headers=optional_headers,
environment_dependency=settings.get_gdal_config,
process_dependency=PostProcessParams,
Expand All @@ -80,7 +80,7 @@ async def lifespan(app: FastAPI):
# add /bbox [GET] and /feature [POST] (default to False)
add_part=True,
)
app.include_router(mosaic.router, prefix=f"{path_prefix}/mosaic", tags=["Mosaic"])
app.include_router(mosaic.router, prefix="/mosaic", tags=["Mosaic"])
# TODO
# prefix will be replaced by `/mosaics/{search_id}` in titiler-pgstac 0.9.0

Expand All @@ -91,22 +91,22 @@ async def lifespan(app: FastAPI):
reader=PgSTACReader,
path_dependency=ItemPathParams,
optional_headers=optional_headers,
router_prefix=f"{path_prefix}/stac",
router_prefix="/stac",
environment_dependency=settings.get_gdal_config,
router=APIRouter(route_class=LoggerRouteHandler),
extensions=[
stacViewerExtension(),
],
)
app.include_router(stac.router, tags=["Items"], prefix=f"{path_prefix}/stac")
app.include_router(stac.router, tags=["Items"], prefix="/stac")
# TODO
# in titiler-pgstac we replaced the prefix to `/collections/{collection_id}/items/{item_id}`

###############################################################################
# /cog - External Cloud Optimized GeoTIFF endpoints
###############################################################################
cog = TilerFactory(
router_prefix=f"{path_prefix}/cog",
router_prefix="/cog",
optional_headers=optional_headers,
environment_dependency=settings.get_gdal_config,
router=APIRouter(route_class=LoggerRouteHandler),
Expand All @@ -116,9 +116,7 @@ async def lifespan(app: FastAPI):
],
)

app.include_router(
cog.router, tags=["Cloud Optimized GeoTIFF"], prefix=f"{path_prefix}/cog"
)
app.include_router(cog.router, tags=["Cloud Optimized GeoTIFF"], prefix="/cog")


@app.get("/healthz", description="Health Check", tags=["Health Check"])
Expand Down
2 changes: 1 addition & 1 deletion raster_api/runtime/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ApiSettings(BaseSettings):
cors_origins: str = "*"
cachecontrol: str = "public, max-age=3600"
debug: bool = False
path_prefix: str = ""
root_path: Optional[str] = None

# MosaicTiler settings
enable_mosaic_search: bool = False
Expand Down
2 changes: 2 additions & 0 deletions routes/infrastructure/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def __init__(
),
cache_policy=cf.CachePolicy.CACHING_DISABLED,
allowed_methods=cf.AllowedMethods.ALLOW_ALL,
origin_request_policy=cf.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
),
"/api/raster*": cf.BehaviorOptions(
origin=origins.HttpOrigin(
Expand All @@ -79,6 +80,7 @@ def __init__(
),
cache_policy=cf.CachePolicy.CACHING_DISABLED,
allowed_methods=cf.AllowedMethods.ALLOW_ALL,
origin_request_policy=cf.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
),
"/api/ingest*": cf.BehaviorOptions(
origin=origins.HttpOrigin(
Expand Down
Loading

0 comments on commit dd00f5a

Please sign in to comment.