Skip to content

Commit f63f24f

Browse files
authored
Merge pull request #15 from benoitdechateauvieux/main
Fixes UI stack circular deployment issue
2 parents c82e08c + bbe52ca commit f63f24f

File tree

9 files changed

+55
-109
lines changed

9 files changed

+55
-109
lines changed

ui/geofm-demo-stack/lib/backend-stack.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2';
1414
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
1515
import { CfnWebACL, CfnWebACLAssociation } from 'aws-cdk-lib/aws-wafv2';
1616

17+
import * as s3 from 'aws-cdk-lib/aws-s3';
18+
import * as cf from 'aws-cdk-lib/aws-cloudfront';
19+
1720

1821
export interface BackendStackProps extends NestedStackProps {
1922
readonly userPool: cognito.IUserPool;
@@ -25,8 +28,9 @@ export interface BackendStackProps extends NestedStackProps {
2528

2629
export class BackendStack extends NestedStack {
2730
public readonly tilesApi: apigw.RestApi;
28-
public readonly s3VpcPointId: string;
2931
public readonly vpc: ec2.Vpc;
32+
public readonly geoTiffBucket: s3.IBucket;
33+
public readonly geoTiffOriginAccessIdentity: cf.OriginAccessIdentity;
3034

3135
constructor(scope: Construct, id: string, props: BackendStackProps) {
3236
super(scope, id, props);
@@ -74,7 +78,6 @@ export class BackendStack extends NestedStack {
7478
actions: ['s3:GetObject'],
7579
resources: ['*']
7680
}));
77-
this.s3VpcPointId = s3AccessPoint.vpcEndpointId;
7881

7982
const tilTilerSG = new ec2.SecurityGroup(this, 'lambdaTilTilerSecurityGroup', {
8083
vpc,
@@ -94,6 +97,37 @@ export class BackendStack extends NestedStack {
9497
description: 'subnet group for geofm-demo redis'
9598
});
9699

100+
101+
this.geoTiffBucket = new s3.Bucket(this, 'GeoTiffBucket', {
102+
bucketName: `aws-geofm-geotiff-bucket-${this.account}-${this.region}-${props.envName}`,
103+
blockPublicAccess: {
104+
blockPublicAcls: true,
105+
restrictPublicBuckets: true,
106+
blockPublicPolicy: true,
107+
ignorePublicAcls: true
108+
},
109+
removalPolicy: RemovalPolicy.DESTROY,
110+
autoDeleteObjects: true,
111+
accessControl: s3.BucketAccessControl.PRIVATE,
112+
enforceSSL: true,
113+
});
114+
115+
this.geoTiffBucket.addToResourcePolicy(new iam.PolicyStatement({
116+
actions: ['s3:GetObject'],
117+
principals: [new iam.AccountPrincipal(this.account)],
118+
resources: [this.geoTiffBucket.arnForObjects('*')],
119+
conditions: {
120+
StringEquals: {
121+
"aws:sourceVpce": `${s3AccessPoint.vpcEndpointId}`
122+
}
123+
}
124+
}));
125+
126+
// Allow direct access to GeoTiff images from the bucket using OAI
127+
this.geoTiffOriginAccessIdentity = new cf.OriginAccessIdentity(this, 'GeoTiffOriginAccessIdentity');
128+
this.geoTiffBucket.grantRead(this.geoTiffOriginAccessIdentity);
129+
130+
97131
const redisCluster = new elasticache.CfnCacheCluster(
98132
this,
99133
"redisCluster", {

ui/geofm-demo-stack/lib/frontend-stack.ts

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,17 @@ export interface FrontendStackStackProps extends NestedStackProps {
1919
readonly customHeaderName: string;
2020
readonly customHeaderValue: string;
2121
readonly tilesApi: apigw.RestApi;
22-
readonly geotiffBucketVpcEndpointId: string;
2322
readonly solaraSGs: string[];
2423
readonly solaraOriginLBDnsName: string;
2524
readonly envName: string;
25+
readonly geoTiffBucket: s3.IBucket;
26+
readonly geoTiffOriginAccessIdentity: cf.OriginAccessIdentity;
2627
}
2728

2829
export class FrontendStack extends NestedStack {
2930

3031
public readonly staticContentBucket: s3.Bucket;
31-
public readonly geoTiffBucket: s3.Bucket;
3232
public readonly frontendUrl: string;
33-
public readonly geotiffUrl: string;
34-
public readonly geotiffBucketName: string;
3533
public readonly cloudFrontDistribution: cf.Distribution;
3634
public readonly authorizerFunction: cf.experimental.EdgeFunction;
3735

@@ -55,38 +53,6 @@ export class FrontendStack extends NestedStack {
5553
const staticContentOriginAccessIdentity = new cf.OriginAccessIdentity(this, 'StaticContentOriginAccessIdentity');
5654
this.staticContentBucket.grantRead(staticContentOriginAccessIdentity);
5755

58-
this.geoTiffBucket = new s3.Bucket(this, 'GeoTiffBucket', {
59-
bucketName: `aws-geofm-geotiff-bucket-${this.account}-${this.region}-${props.envName}`,
60-
blockPublicAccess: {
61-
blockPublicAcls: true,
62-
restrictPublicBuckets: true,
63-
blockPublicPolicy: true,
64-
ignorePublicAcls: true
65-
},
66-
removalPolicy: RemovalPolicy.DESTROY,
67-
autoDeleteObjects: true,
68-
accessControl: s3.BucketAccessControl.PRIVATE,
69-
enforceSSL: true,
70-
});
71-
72-
this.geotiffUrl = this.geoTiffBucket.urlForObject();
73-
this.geotiffBucketName = this.geoTiffBucket.bucketName;
74-
75-
this.geoTiffBucket.addToResourcePolicy(new iam.PolicyStatement({
76-
actions: ['s3:GetObject'],
77-
principals: [new iam.AccountPrincipal(this.account)],
78-
resources: [ this.geoTiffBucket.arnForObjects('*') ],
79-
conditions: {
80-
StringEquals: {
81-
"aws:sourceVpce": `${props.geotiffBucketVpcEndpointId}`
82-
}
83-
}
84-
}));
85-
86-
// Allow direct access to GeoTiff images from the bucket using OAI
87-
const geoTiffOriginAccessIdentity = new cf.OriginAccessIdentity(this, 'GeoTiffOriginAccessIdentity');
88-
this.geoTiffBucket.grantRead(geoTiffOriginAccessIdentity);
89-
9056
this.authorizerFunction = new cloudfront.experimental.EdgeFunction(this, 'CloudFrontAuthorizer', {
9157
runtime: lambda.Runtime.NODEJS_18_X,
9258
handler: 'index.handler',
@@ -188,8 +154,8 @@ export class FrontendStack extends NestedStack {
188154
});
189155

190156
// The Geotiff images are located in geotiff/*.tif
191-
this.cloudFrontDistribution.addBehavior('geotiff/*', new cfo.S3Origin(this.geoTiffBucket, {
192-
originAccessIdentity: geoTiffOriginAccessIdentity
157+
this.cloudFrontDistribution.addBehavior('geotiff/*', new cfo.S3Origin(props.geoTiffBucket, {
158+
originAccessIdentity: props.geoTiffOriginAccessIdentity
193159
}),
194160
{
195161
edgeLambdas: edgeLambda,

ui/geofm-demo-stack/lib/geofm-demo-stack.ts

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export class GeoFMDemoStack extends cdk.Stack {
3333
});
3434

3535
const solaraBackend = new SolaraFEStack(this, 'SolaraFEStack', {
36-
// cloudFrontDistribution: frontend.cloudFrontDistribution,
3736
customHeaderName: secureHeaderName,
3837
customHeaderValue: secureHeaderValue,
3938
envName: props?.envName || 'dev',
39+
geoTiffBucket: backend.geoTiffBucket
4040
});
4141

4242
const frontend = new FrontendStack(this, 'FrontendStack', {
@@ -45,8 +45,9 @@ export class GeoFMDemoStack extends cdk.Stack {
4545
customHeaderName: secureHeaderName,
4646
customHeaderValue: secureHeaderValue,
4747
tilesApi: backend.tilesApi,
48-
geotiffBucketVpcEndpointId: backend.s3VpcPointId,
49-
envName: props?.envName || 'dev'
48+
envName: props?.envName || 'dev',
49+
geoTiffBucket: backend.geoTiffBucket,
50+
geoTiffOriginAccessIdentity: backend.geoTiffOriginAccessIdentity
5051
});
5152

5253
this.applyConfiguration(frontend, auth);
@@ -96,32 +97,5 @@ export class GeoFMDemoStack extends cdk.Stack {
9697
installLatestAwsSdk: false
9798
});
9899
customResourceUserPoolClientConfig.node.addDependency(frontend.cloudFrontDistribution, auth.userPoolClient);
99-
100-
const config = {
101-
tiles_backend_url: frontend.frontendUrl + '/tile/cog/tiles',
102-
cloudfront_url: frontend.frontendUrl,
103-
geotiff_bucket_url: frontend.geotiffUrl
104-
};
105-
106-
const putConfigCall: cr.AwsSdkCall = {
107-
service: 'S3',
108-
action: 'putObject',
109-
parameters: {
110-
Bucket: frontend.staticContentBucket.bucketName,
111-
Key: 'config.json',
112-
Body: JSON.stringify(config),
113-
},
114-
physicalResourceId: cr.PhysicalResourceId.of(frontend.staticContentBucket.bucketArn),
115-
};
116-
117-
const customResourceFrontendConfig = new cr.AwsCustomResource(this, 'PutFrontendConfig', {
118-
onCreate: putConfigCall,
119-
onUpdate: putConfigCall,
120-
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
121-
resources: [frontend.staticContentBucket.bucketArn + '/*']
122-
}),
123-
installLatestAwsSdk: false
124-
});
125-
customResourceFrontendConfig.node.addDependency(frontend.geoTiffBucket, frontend.cloudFrontDistribution);
126100
}
127101
}

ui/geofm-demo-stack/lib/solara-fe-stack.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface SolaraFEStackProps extends cdk.NestedStackProps {
2020
customHeaderName: string;
2121
customHeaderValue: string;
2222
envName: string;
23+
geoTiffBucket: s3.IBucket;
2324
// userPool: cognito.IUserPool;
2425
// authorizerFunction: cloudfront.experimental.EdgeFunction;
2526
}
@@ -129,7 +130,10 @@ export class SolaraFEStack extends cdk.NestedStack {
129130
logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'SolaraBackend' }),
130131
environment: {
131132
'SOLARA_APP': 'app:app',
132-
'SOLARA_ASSETS_PREFIX': '/solara/'
133+
'SOLARA_ASSETS_PREFIX': '/solara/',
134+
"GEOTIFF_BUCKET_URL": props.geoTiffBucket.urlForObject(),
135+
"TILES_BACKEND_URL": "/tile/cog/tiles",
136+
"CLOUDFRONT_URL": "/",
133137
}
134138
});
135139

ui/geofm-demo-stack/solara-fe/imagery_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Optional, Union, Any
44
from dataclasses import dataclass
55
import logging
6+
import os
67

78
logger = logging.getLogger(__name__)
89

@@ -151,7 +152,7 @@ def convert_sentinel_id(sentinel_id):
151152
)
152153
else:
153154
geotiff_url = (
154-
f"{config.geotiff_bucket_url}/geotiff/{config.aoi_name}/"
155+
f"{os.environ.get('GEOTIFF_BUCKET_URL')}/geotiff/{config.aoi_name}/"
155156
f"{img_meta.provider.folder_structure}/{year}/{img_meta.month}/"
156157
f"{img_meta.get_image_path()}_aoi_max_size_{band_name}.tif"
157158
)
@@ -163,7 +164,7 @@ def convert_sentinel_id(sentinel_id):
163164
if not config.use_remote_s2_cogs:
164165
geotiff_url = f"{geotiff_url}&{viz_params}"
165166

166-
return f"{config.tiles_backend_url}/{{z}}/{{x}}/{{y}}.png?url={geotiff_url}"
167+
return f"{os.environ.get('TILES_BACKEND_URL')}/{{z}}/{{x}}/{{y}}.png?url={geotiff_url}"
167168

168169
except Exception as e:
169170
logger.error(f"Error generating layer URL: {str(e)}")

ui/geofm-demo-stack/solara-fe/pages/01-home.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def LandingPage():
4949

5050
#FOR DEPLOYMENT
5151
demo_config = load_config()
52-
cloudfront_url = demo_config["config"]["cloudfront_url"]
52+
cloudfront_url = os.environ.get('CLOUDFRONT_URL')
5353
background_video_url = f"{cloudfront_url}/{demo_config['config']['background_video_file']}"
5454

5555
with solara.Column(style={"height": "100vh", "width": "100vw", "position": "relative", "overflow": "hidden"}):

ui/geofm-demo-stack/solara-fe/pages/02-run_similarity_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def create_base_layers(self):
7777
max_zoom=24,
7878
name='Sentinel-2',
7979
bounds=self.aoi_bounds,
80-
url=f"{self.config.tiles_backend_url}/{{z}}/{{x}}/{{y}}.png?url={self.config.sentinel2_cog_source_url}"
80+
url=f"{os.environ.get('TILES_BACKEND_URL')}/{{z}}/{{x}}/{{y}}.png?url={self.config.sentinel2_cog_source_url}"
8181
)
8282

8383
chip_grid = self._create_chip_grid()

ui/geofm-demo-stack/solara-fe/pages/03-detect_change.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def create_base_layers(self):
122122
max_zoom=24,
123123
name='Sentinel-2',
124124
bounds=self.aoi_bounds,
125-
url=f"{self.config.tiles_backend_url}/{{z}}/{{x}}/{{y}}.png?url={self.config.sentinel2_cog_source_url}"
125+
url=f"{os.environ.get('TILES_BACKEND_URL')}/{{z}}/{{x}}/{{y}}.png?url={self.config.sentinel2_cog_source_url}"
126126
)
127127

128128
chip_grid = self._create_chip_grid()

ui/geofm-demo-stack/solara-fe/utils.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,6 @@
66
s3_res = boto3.resource('s3')
77

88

9-
def read_s3_config(bucket_name: str, key: str):
10-
"""
11-
Reads the frontend JSON config created by CDK.
12-
13-
Args:
14-
bucket_name: Bucket containing the FE config JSON file (i.e. geofm-demo-xxx-us-west-2-dev)
15-
key: config file name (i.e config.json)
16-
17-
Returns:
18-
Dict containing the FE configuration
19-
{
20-
"tiles_backend_url": "https://xxx.cloudfront.net/tile/cog/tiles",
21-
"cloudfront_url":"https://xxx.cloudfront.net/api/",
22-
"geotiff_bucket_url":"https://s3.us-west-2.amazonaws.com/geofm-demo-xxx-us-west-2-dev-geotiff"
23-
}
24-
"""
25-
s3_client = boto3.client('s3')
26-
27-
try:
28-
response = s3_client.get_object(Bucket=bucket_name, Key=key)
29-
json_content = json.loads(response['Body'].read().decode('utf-8'))
30-
return json_content
31-
except Exception as e:
32-
print(f"Error reading FE JSON from S3: {str(e)}")
33-
return None
34-
359
# Custom decoder for "True"/"False" strings
3610
def bool_decoder(dct):
3711
for k, v in dct.items():
@@ -60,13 +34,6 @@ def load_config(config_path: str = 'demo_config.json', demo_id: int = None) -> D
6034
demo_config = [obj for obj in config["demos"] if obj["demo_id"] == demo_id]
6135

6236
if len(demo_config) > 0:
63-
# read FE config created by CDK and add missing values
64-
fe_config = read_s3_config(demo_config[0]["config"]["fe_bucket_name"], "config.json")
65-
66-
# add new keys to the config
67-
if fe_config != None:
68-
for k in fe_config:
69-
demo_config[0]["config"][k] = fe_config[k]
7037
return demo_config[0]
7138
else:
7239
raise Exception("Demo not found")

0 commit comments

Comments
 (0)