Skip to content

Commit 36499d2

Browse files
authored
feat: add STAC browser option (#64)
* feat: add STAC browser option * automate the build process within the construct, parameterize stac catalog url and radiant earth repo tag * option to use existing bucket, option to choose clone directory, avoid deleting anything and raise an error if using an existing directory does not work
1 parent c5b3605 commit 36499d2

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ A STAC API implementation using [stac-fastapi](https://github.com/stac-utils/sta
1717
### [pgSTAC Titiler API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
1818
A complete dynamic tiling API using [titiler-pgstac](https://github.com/stac-utils/titiler-pgstac) to create dynamic mosaics of assets based on [STAC Search queries](https://github.com/radiantearth/stac-api-spec/tree/master/item-search). Packaged as a complete runtime for deployment with API Gateway and Lambda and fully integrated with the pgSTAC Database construct.
1919

20+
### [STAC browser](https://developmentseed.org/eoapi-cdk/#stacbrowser-)
21+
A CDK construct to host a static [Radiant Earth STAC browser](https://github.com/radiantearth/stac-browser) on S3.
22+
2023
### [OGC Features/Tiles API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
2124
A complete OGC Features/Tiles API using [tipg](https://github.com/developmentseed/tipg). Packaged as a complete runtime for deployment with API Gateway and Lambda. By default the API will be connected to the Database's `public` schema.
2225

lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export * from "./database";
44
export * from "./ingestor-api";
55
export * from "./stac-api";
66
export * from "./titiler-pgstac-api";
7+
export * from "./stac-browser";
78
export * from "./tipg-api";

lib/stac-browser/index.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { Stack, aws_s3 as s3, aws_s3_deployment as s3_deployment} from "aws-cdk-lib";
2+
import { RemovalPolicy, CfnOutput } from "aws-cdk-lib";
3+
import { PolicyStatement, ServicePrincipal, Effect } from "aws-cdk-lib/aws-iam";
4+
5+
import { Construct } from "constructs";
6+
import { execSync } from "child_process";
7+
import * as fs from 'fs';
8+
9+
const DEFAULT_CLONE_DIRECTORY = './stac-browser';
10+
11+
export class StacBrowser extends Construct {
12+
13+
public bucket: s3.IBucket;
14+
public bucketDeployment: s3_deployment.BucketDeployment;
15+
16+
constructor(scope: Construct, id: string, props: StacBrowserProps) {
17+
super(scope, id);
18+
19+
const buildPath = this.buildApp(props.stacCatalogUrl, props.githubRepoTag, props.cloneDirectory || DEFAULT_CLONE_DIRECTORY);
20+
21+
// import a bucket from props.bucketArn if defined, otherwise create a new bucket
22+
if (props.bucketArn) {
23+
this.bucket = s3.Bucket.fromBucketArn(this, 'Bucket', props.bucketArn);
24+
} else {
25+
this.bucket = new s3.Bucket(this, 'Bucket', {
26+
accessControl: s3.BucketAccessControl.PRIVATE,
27+
removalPolicy: RemovalPolicy.DESTROY,
28+
websiteIndexDocument: props.websiteIndexDocument
29+
})
30+
}
31+
32+
// if props.cloudFrontDistributionArn is defined and props.bucketArn is not defined, add a bucket policy to allow read access from the cloudfront distribution
33+
if (props.cloudFrontDistributionArn && !props.bucketArn) {
34+
this.bucket.addToResourcePolicy(new PolicyStatement({
35+
sid: 'AllowCloudFrontServicePrincipal',
36+
effect: Effect.ALLOW,
37+
actions: ['s3:GetObject'],
38+
principals: [new ServicePrincipal('cloudfront.amazonaws.com')],
39+
resources: [this.bucket.arnForObjects('*')],
40+
conditions: {
41+
'StringEquals': {
42+
'aws:SourceArn': props.cloudFrontDistributionArn
43+
}
44+
}
45+
}));
46+
}
47+
48+
// add the compiled code to the bucket as a bucket deployment
49+
this.bucketDeployment = new s3_deployment.BucketDeployment(this, 'BucketDeployment', {
50+
destinationBucket: this.bucket,
51+
sources: [s3_deployment.Source.asset(buildPath)]
52+
});
53+
54+
new CfnOutput(this, "bucket-name", {
55+
exportName: `${Stack.of(this).stackName}-bucket-name`,
56+
value: this.bucket.bucketName,
57+
});
58+
59+
}
60+
61+
private buildApp(stacCatalogUrl: string, githubRepoTag: string, cloneDirectory: string): string {
62+
63+
// Define where to clone and build
64+
const githubRepoUrl = 'https://github.com/radiantearth/stac-browser.git';
65+
66+
67+
// Maybe the repo already exists in cloneDirectory. Try checking out the desired version and if it fails, delete and reclone.
68+
try {
69+
console.log(`Checking if a valid cloned repo exists with version ${githubRepoTag}...`)
70+
execSync(`git checkout tags/${githubRepoTag}`, { cwd: cloneDirectory });
71+
}
72+
catch (error) {
73+
74+
// if directory exists, raise an error
75+
if (fs.existsSync(cloneDirectory)) {
76+
throw new Error(`Directory ${cloneDirectory} already exists and is not a valid clone of ${githubRepoUrl}. Please delete this directory or specify a different cloneDirectory.`);
77+
}
78+
79+
// else, we clone and check out the version.
80+
81+
// Clone the repo
82+
console.log(`Cloning ${githubRepoUrl} into ${cloneDirectory}...`)
83+
execSync(`git clone ${githubRepoUrl} ${cloneDirectory}`);
84+
85+
// Check out the desired version
86+
console.log(`Checking out version ${githubRepoTag}...`)
87+
execSync(`git checkout tags/${githubRepoTag}`, { cwd: cloneDirectory });
88+
89+
}
90+
91+
// Install the dependencies and build the application
92+
console.log(`Installing dependencies`)
93+
execSync('npm install', { cwd: cloneDirectory });
94+
95+
// Build the app with catalogUrl
96+
console.log(`Building app with catalogUrl=${stacCatalogUrl} into ${cloneDirectory}`)
97+
execSync(`npm run build -- --catalogUrl=${stacCatalogUrl}`, { cwd: cloneDirectory });
98+
99+
return './stac-browser/dist'
100+
101+
}
102+
103+
104+
}
105+
106+
export interface StacBrowserProps {
107+
108+
/**
109+
* Bucket ARN. If specified, the identity used to deploy the stack must have the appropriate permissions to create a deployment for this bucket.
110+
* In addition, if specified, `cloudFrontDistributionArn` is ignored since the policy of an imported resource can't be modified.
111+
*
112+
* @default - No bucket ARN. A new bucket will be created.
113+
*/
114+
115+
readonly bucketArn?: string;
116+
117+
/**
118+
* STAC catalog URL
119+
*/
120+
readonly stacCatalogUrl: string;
121+
122+
/**
123+
* Tag of the radiant earth stac-browser repo to use to build the app.
124+
*/
125+
readonly githubRepoTag: string;
126+
127+
128+
/**
129+
* The ARN of the cloudfront distribution that will be added to the bucket policy with read access.
130+
* If `bucketArn` is specified, this parameter is ignored since the policy of an imported bucket can't be modified.
131+
*
132+
* @default - No cloudfront distribution ARN. The bucket policy will not be modified.
133+
*/
134+
readonly cloudFrontDistributionArn?: string;
135+
136+
/**
137+
* The name of the index document (e.g. "index.html") for the website. Enables static website
138+
* hosting for this bucket.
139+
*
140+
* @default - No index document.
141+
*/
142+
readonly websiteIndexDocument?: string;
143+
144+
/**
145+
* Location in the filesystem where to compile the browser code.
146+
*
147+
* @default - DEFAULT_CLONE_DIRECTORY
148+
*/
149+
readonly cloneDirectory?: string;
150+
151+
}

0 commit comments

Comments
 (0)