44import os
55from typing import Any
66
7+ import boto3
78from aws_cdk import App , CfnOutput , Duration , RemovalPolicy , Stack , Tags
89from aws_cdk import aws_ec2 as ec2
10+ from aws_cdk import aws_iam as iam
911from aws_cdk import aws_lambda
1012from aws_cdk import aws_logs as logs
1113from aws_cdk import aws_rds as rds
14+ from aws_cdk import aws_s3 as s3
1215from config import (
1316 eoAPISettings ,
1417 eoDBSettings ,
2023from eoapi_cdk import (
2124 PgStacApiLambda ,
2225 PgStacDatabase ,
26+ StacBrowser ,
27+ StacIngestor ,
2328 TiPgApiLambda ,
2429 TitilerPgstacApiLambda ,
2530)
@@ -42,8 +47,6 @@ def __init__( # noqa: C901
4247 """Define stack."""
4348 super ().__init__ (scope , id , ** kwargs )
4449
45- # vpc = ec2.Vpc(self, f"{id}-vpc", nat_gateways=0)
46-
4750 vpc = ec2 .Vpc (
4851 self ,
4952 f"{ id } -vpc" ,
@@ -52,23 +55,8 @@ def __init__( # noqa: C901
5255 name = "ingress" ,
5356 cidr_mask = 24 ,
5457 subnet_type = ec2 .SubnetType .PUBLIC ,
55- ),
56- ec2 .SubnetConfiguration (
57- name = "application" ,
58- cidr_mask = 24 ,
59- subnet_type = ec2 .SubnetType .PRIVATE_WITH_EGRESS ,
60- ),
61- ec2 .SubnetConfiguration (
62- name = "rds" ,
63- cidr_mask = 28 ,
64- subnet_type = ec2 .SubnetType .PRIVATE_ISOLATED ,
65- ),
58+ )
6659 ],
67- nat_gateways = 1 ,
68- )
69- print (
70- """The eoAPI stack use AWS NatGateway for the Raster service so it can reach the internet.
71- This might incurs some cost (https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html)."""
7260 )
7361
7462 interface_endpoints = [
@@ -135,6 +123,9 @@ def __init__( # noqa: C901
135123 secrets_prefix = os .path .join (stage , name ),
136124 )
137125
126+ # allow connections from anywhere to the DB
127+ pgstac_db .db .connections .allow_default_port_from_any_ipv4 ()
128+
138129 CfnOutput (
139130 self ,
140131 f"{ id } -database-secret-arn" ,
@@ -174,10 +165,6 @@ def __init__( # noqa: C901
174165 f"{ id } -raster-lambda" ,
175166 db = pgstac_db .db ,
176167 db_secret = pgstac_db .pgstac_secret ,
177- vpc = vpc ,
178- subnet_selection = ec2 .SubnetSelection (
179- subnet_type = ec2 .SubnetType .PRIVATE_WITH_EGRESS
180- ),
181168 api_env = env ,
182169 lambda_function_options = {
183170 "code" : aws_lambda .Code .from_docker_build (
@@ -232,15 +219,11 @@ def __init__( # noqa: C901
232219 if "raster" in eoapi_settings .functions :
233220 env ["TITILER_ENDPOINT" ] = eoraster .url .strip ("/" )
234221
235- PgStacApiLambda (
222+ eostac = PgStacApiLambda (
236223 self ,
237224 id = f"{ id } -stac-lambda" ,
238225 db = pgstac_db .db ,
239226 db_secret = pgstac_db .pgstac_secret ,
240- vpc = vpc ,
241- subnet_selection = ec2 .SubnetSelection (
242- subnet_type = ec2 .SubnetType .PRIVATE_WITH_EGRESS
243- ),
244227 api_env = env ,
245228 lambda_function_options = {
246229 "runtime" : aws_lambda .Runtime .PYTHON_3_11 ,
@@ -259,6 +242,38 @@ def __init__( # noqa: C901
259242 },
260243 )
261244
245+ if eostac_settings .stac_browser_github_tag is not None :
246+ assert (
247+ eostac_settings .stac_api_custom_domain_name is not None
248+ ), "stac_api_custom_domain_name must be set if stac_browser_github_tag is not None. The browser deployment needs a resolved STAC API url at deployment time and so needs to rely on a predefined custom domain name."
249+ stac_browser_bucket = s3 .Bucket (
250+ self ,
251+ "stac-browser-bucket" ,
252+ bucket_name = f"{ id .lower ()} -stac-browser" ,
253+ removal_policy = RemovalPolicy .DESTROY ,
254+ auto_delete_objects = True ,
255+ website_index_document = "index.html" ,
256+ public_read_access = True ,
257+ block_public_access = s3 .BlockPublicAccess (
258+ block_public_acls = False ,
259+ block_public_policy = False ,
260+ ignore_public_acls = False ,
261+ restrict_public_buckets = False ,
262+ ),
263+ object_ownership = s3 .ObjectOwnership .OBJECT_WRITER ,
264+ )
265+
266+ # need to build this manually, the attribute eostac.url is not resolved yet.
267+
268+ StacBrowser (
269+ self ,
270+ "stac-browser" ,
271+ github_repo_tag = eostac_settings .stac_browser_github_tag ,
272+ stac_catalog_url = eostac_settings .stac_api_custom_domain_name ,
273+ website_index_document = "index.html" ,
274+ bucket_arn = stac_browser_bucket .bucket_arn ,
275+ )
276+
262277 # eoapi.vector
263278 if "vector" in eoapi_settings .functions :
264279 db_secrets = {
@@ -292,12 +307,8 @@ def __init__( # noqa: C901
292307 TiPgApiLambda (
293308 self ,
294309 f"{ id } -vector-lambda" ,
295- vpc = vpc ,
296310 db = pgstac_db .db ,
297311 db_secret = pgstac_db .pgstac_secret ,
298- subnet_selection = ec2 .SubnetSelection (
299- subnet_type = ec2 .SubnetType .PRIVATE_WITH_EGRESS
300- ),
301312 api_env = env ,
302313 lambda_function_options = {
303314 "runtime" : aws_lambda .Runtime .PYTHON_3_11 ,
@@ -316,6 +327,72 @@ def __init__( # noqa: C901
316327 },
317328 )
318329
330+ if "ingestor" in eoapi_settings .functions :
331+
332+ data_access_role = self ._create_data_access_role ()
333+
334+ stac_ingestor = StacIngestor (
335+ self ,
336+ "stac-ingestor" ,
337+ stac_url = eostac .url ,
338+ stage = eoapi_settings .stage ,
339+ data_access_role = data_access_role ,
340+ stac_db_secret = pgstac_db .pgstac_secret ,
341+ stac_db_security_group = pgstac_db .db .connections .security_groups [0 ],
342+ api_env = {"REQUESTER_PAYS" : "True" },
343+ )
344+
345+ data_access_role = self ._grant_assume_role_with_principal_pattern (
346+ data_access_role , stac_ingestor .handler_role .role_name
347+ )
348+
349+ def _create_data_access_role (self ) -> iam .Role :
350+
351+ """
352+ Creates an IAM role with full S3 read access.
353+ """
354+
355+ data_access_role = iam .Role (
356+ self ,
357+ "data-access-role" ,
358+ assumed_by = iam .ServicePrincipal ("lambda.amazonaws.com" ),
359+ )
360+
361+ data_access_role .add_managed_policy (
362+ iam .ManagedPolicy .from_aws_managed_policy_name ("AmazonS3FullAccess" )
363+ )
364+
365+ return data_access_role
366+
367+ def _grant_assume_role_with_principal_pattern (
368+ self ,
369+ role_to_assume : iam .Role ,
370+ principal_pattern : str ,
371+ account_id : str = boto3 .client ("sts" ).get_caller_identity ().get ("Account" ),
372+ ) -> iam .Role :
373+ """
374+ Grants assume role permissions to the role of the given
375+ account with the given name pattern. Default account
376+ is the current account.
377+ """
378+
379+ role_to_assume .assume_role_policy .add_statements (
380+ iam .PolicyStatement (
381+ effect = iam .Effect .ALLOW ,
382+ principals = [iam .AnyPrincipal ()],
383+ actions = ["sts:AssumeRole" ],
384+ conditions = {
385+ "StringLike" : {
386+ "aws:PrincipalArn" : [
387+ f"arn:aws:iam::{ account_id } :role/{ principal_pattern } "
388+ ]
389+ }
390+ },
391+ )
392+ )
393+
394+ return role_to_assume
395+
319396
320397app = App ()
321398
0 commit comments