Skip to content

Commit e1feeb2

Browse files
Add tipg, CDNs and STAC browser, make bastion host optional, update README, bump eoapi-cdk, clean up the code (#12)
* add tipg and bump eoapi-cdk * bump * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * make bastion host optional and avoid rewriting all the params in app wrapper just use the config as kwargs * readme * add CDN options * add basic browser * make bucket public --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 15003b0 commit e1feeb2

File tree

6 files changed

+273
-79
lines changed

6 files changed

+273
-79
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,33 @@
11
# eoapi-template
2+
23
Demonstration application showing the use and configuration options of the [eoapi-cdk constructs](https://github.com/developmentseed/eoapi-cdk) on AWS.
4+
5+
## Requirements
6+
7+
- python
8+
- docker
9+
- the AWS CDK CLI
10+
- AWS credentials environment variables configured to point to an account.
11+
- **Optional** a `config.yaml` file to override the default deployment settings defined in `config.py`.
12+
13+
## Installation
14+
15+
```
16+
python -m venv .venv
17+
source .venv/bin/activate
18+
python -m pip install -r requirements.txt
19+
```
20+
21+
## Deployment
22+
23+
First, synthesize the app
24+
25+
```
26+
cdk synth --all
27+
```
28+
29+
Then, deploy
30+
31+
```
32+
cdk deploy --all --require-approval never
33+
```

app.py

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,17 @@
1-
import yaml
21
from aws_cdk import App
32

4-
from config import Config
3+
from config import build_app_config
54
from eoapi_template import pgStacInfra, vpc
65

76
app = App()
87

9-
try:
10-
with open("config.yaml") as f:
11-
config = yaml.safe_load(f)
12-
config = (
13-
{} if config is None else config
14-
) # if config is empty, set it to an empty dict
15-
config = Config(**config)
16-
except FileNotFoundError:
17-
# if no config at the expected path, using defaults
18-
config = Config()
19-
20-
vpc_stack = vpc.VpcStack(
21-
tags=config.tags,
22-
scope=app,
23-
id=config.build_service_name("pgSTAC-vpc"),
24-
nat_gateway_count=config.nat_gateway_count,
25-
)
8+
app_config = build_app_config()
269

10+
vpc_stack = vpc.VpcStack(scope=app, app_config=app_config)
2711

2812
pgstac_infra_stack = pgStacInfra.pgStacInfraStack(
2913
scope=app,
30-
tags=config.tags,
31-
id=config.build_service_name("pgSTAC-infra"),
3214
vpc=vpc_stack.vpc,
33-
stac_api_lambda_name=config.build_service_name("STAC API"),
34-
titiler_pgstac_api_lambda_name=config.build_service_name("titiler pgSTAC API"),
35-
stage=config.stage,
36-
db_allocated_storage=config.db_allocated_storage,
37-
public_db_subnet=config.public_db_subnet,
38-
db_instance_type=config.db_instance_type,
39-
bastion_host_allow_ip_list=config.bastion_host_allow_ip_list,
40-
bastion_host_create_elastic_ip=config.bastion_host_create_elastic_ip,
41-
bastion_host_user_data=yaml.dump(config.bastion_host_user_data),
42-
titiler_buckets=config.titiler_buckets,
43-
data_access_role_arn=config.data_access_role_arn,
44-
auth_provider_jwks_url=config.auth_provider_jwks_url,
15+
app_config=app_config,
4516
)
4617
app.synth()

config.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Any, Dict, List, Optional, Union
22

33
import pydantic
4+
import yaml
45
from aws_cdk import aws_ec2
56
from pydantic_core.core_schema import FieldValidationInfo
67
from pydantic_settings import BaseSettings
@@ -10,7 +11,7 @@
1011
DEFAULT_NAT_GATEWAY_COUNT = 1
1112

1213

13-
class Config(BaseSettings):
14+
class AppConfig(BaseSettings):
1415
project_id: Optional[str] = pydantic.Field(
1516
description="Project ID", default=DEFAULT_PROJECT_ID
1617
)
@@ -54,6 +55,12 @@ class Config(BaseSettings):
5455
description="Number of NAT gateways to create",
5556
default=DEFAULT_NAT_GATEWAY_COUNT,
5657
)
58+
bastion_host: Optional[bool] = pydantic.Field(
59+
description="""Whether to create a bastion host. It can typically
60+
be used to make administrative connections to the database if
61+
`public_db_subnet` is False""",
62+
default=True,
63+
)
5764
bastion_host_create_elastic_ip: Optional[bool] = pydantic.Field(
5865
description="Whether to create an elastic IP for the bastion host",
5966
default=False,
@@ -74,6 +81,39 @@ class Config(BaseSettings):
7481
buckets to grant access to the titiler API""",
7582
default=[],
7683
)
84+
acm_certificate_arn: Optional[str] = pydantic.Field(
85+
description="""ARN of ACM certificate to use for
86+
custom domain names. If provided,
87+
CDNs are created for all the APIs""",
88+
default=None,
89+
)
90+
stac_api_custom_domain: Optional[str] = pydantic.Field(
91+
description="""Custom domain name for the STAC API.
92+
Must provide `acm_certificate_arn`""",
93+
default=None,
94+
)
95+
titiler_pgstac_api_custom_domain: Optional[str] = pydantic.Field(
96+
description="""Custom domain name for the titiler pgstac API.
97+
Must provide `acm_certificate_arn`""",
98+
default=None,
99+
)
100+
stac_ingestor_api_custom_domain: Optional[str] = pydantic.Field(
101+
description="""Custom domain name for the STAC ingestor API.
102+
Must provide `acm_certificate_arn`""",
103+
default=None,
104+
)
105+
tipg_api_custom_domain: Optional[str] = pydantic.Field(
106+
description="""Custom domain name for the tipg API.
107+
Must provide `acm_certificate_arn`""",
108+
default=None,
109+
)
110+
stac_browser_version: Optional[str] = pydantic.Field(
111+
description="""Version of the Radiant Earth STAC browser to deploy.
112+
If none provided, no STAC browser will be deployed.
113+
If provided, `stac_api_custom_domain` must be provided
114+
as it will be used as a backend.""",
115+
default=None,
116+
)
77117

78118
@pydantic.field_validator("tags")
79119
def default_tags(cls, v, info: FieldValidationInfo):
@@ -91,5 +131,50 @@ def validate_nat_gateway_count(cls, v, info: FieldValidationInfo):
91131
else:
92132
return v
93133

134+
@pydantic.field_validator("stac_browser_version")
135+
def validate_stac_browser_version(cls, v, info: FieldValidationInfo):
136+
if v is not None and info.data["stac_api_custom_domain"] is None:
137+
raise ValueError(
138+
"""If a STAC browser version is provided,
139+
a custom domain must be provided for the STAC API"""
140+
)
141+
else:
142+
return v
143+
144+
@pydantic.field_validator("acm_certificate_arn")
145+
def validate_acm_certificate_arn(cls, v, info: FieldValidationInfo):
146+
if v is None and any(
147+
[
148+
info.data["stac_api_custom_domain"],
149+
info.data["titiler_pgstac_api_custom_domain"],
150+
info.data["stac_ingestor_api_custom_domain"],
151+
info.data["tipg_api_custom_domain"],
152+
]
153+
):
154+
raise ValueError(
155+
"""If any custom domain is provided,
156+
an ACM certificate ARN must be provided"""
157+
)
158+
else:
159+
return v
160+
94161
def build_service_name(self, service_id: str) -> str:
95162
return f"{self.project_id}-{self.stage}-{service_id}"
163+
164+
165+
def build_app_config() -> AppConfig:
166+
"""Builds the AppConfig object from config.yaml file if exists,
167+
otherwise use defaults"""
168+
try:
169+
with open("config.yaml") as f:
170+
print("Loading config from config.yaml")
171+
app_config = yaml.safe_load(f)
172+
app_config = (
173+
{} if app_config is None else app_config
174+
) # if config is empty, set it to an empty dict
175+
app_config = AppConfig(**app_config)
176+
except FileNotFoundError:
177+
# if no config at the expected path, using defaults
178+
app_config = AppConfig()
179+
180+
return app_config

0 commit comments

Comments
 (0)