Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add STAC browser option #64

Merged
merged 4 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ A STAC API implementation using [stac-fastapi](https://github.com/stac-utils/sta
### [pgSTAC Titiler API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
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.

### [STAC browser](https://developmentseed.org/eoapi-cdk/#stacbrowser-)
A CDK construct to host a static [Radiant Earth STAC browser](https://github.com/radiantearth/stac-browser) on S3.

### [OGC Features/Tiles API](https://developmentseed.org/eoapi-cdk/#titilerpgstacapilambda-)
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.

Expand Down
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from "./database";
export * from "./ingestor-api";
export * from "./stac-api";
export * from "./titiler-pgstac-api";
export * from "./stac-browser";
export * from "./tipg-api";
151 changes: 151 additions & 0 deletions lib/stac-browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Stack, aws_s3 as s3, aws_s3_deployment as s3_deployment} from "aws-cdk-lib";
import { RemovalPolicy, CfnOutput } from "aws-cdk-lib";
import { PolicyStatement, ServicePrincipal, Effect } from "aws-cdk-lib/aws-iam";

import { Construct } from "constructs";
import { execSync } from "child_process";
import * as fs from 'fs';

const DEFAULT_CLONE_DIRECTORY = './stac-browser';

export class StacBrowser extends Construct {

public bucket: s3.IBucket;
public bucketDeployment: s3_deployment.BucketDeployment;

constructor(scope: Construct, id: string, props: StacBrowserProps) {
super(scope, id);

const buildPath = this.buildApp(props.stacCatalogUrl, props.githubRepoTag, props.cloneDirectory || DEFAULT_CLONE_DIRECTORY);

// import a bucket from props.bucketArn if defined, otherwise create a new bucket
if (props.bucketArn) {
this.bucket = s3.Bucket.fromBucketArn(this, 'Bucket', props.bucketArn);
} else {
this.bucket = new s3.Bucket(this, 'Bucket', {
accessControl: s3.BucketAccessControl.PRIVATE,
removalPolicy: RemovalPolicy.DESTROY,
websiteIndexDocument: props.websiteIndexDocument
})
}

// if props.cloudFrontDistributionArn is defined and props.bucketArn is not defined, add a bucket policy to allow read access from the cloudfront distribution
if (props.cloudFrontDistributionArn && !props.bucketArn) {
this.bucket.addToResourcePolicy(new PolicyStatement({
sid: 'AllowCloudFrontServicePrincipal',
effect: Effect.ALLOW,
actions: ['s3:GetObject'],
principals: [new ServicePrincipal('cloudfront.amazonaws.com')],
resources: [this.bucket.arnForObjects('*')],
conditions: {
'StringEquals': {
'aws:SourceArn': props.cloudFrontDistributionArn
}
}
}));
}

// add the compiled code to the bucket as a bucket deployment
this.bucketDeployment = new s3_deployment.BucketDeployment(this, 'BucketDeployment', {
emileten marked this conversation as resolved.
Show resolved Hide resolved
destinationBucket: this.bucket,
sources: [s3_deployment.Source.asset(buildPath)]
});

new CfnOutput(this, "bucket-name", {
exportName: `${Stack.of(this).stackName}-bucket-name`,
value: this.bucket.bucketName,
});

}

private buildApp(stacCatalogUrl: string, githubRepoTag: string, cloneDirectory: string): string {

// Define where to clone and build
const githubRepoUrl = 'https://github.com/radiantearth/stac-browser.git';


// Maybe the repo already exists in cloneDirectory. Try checking out the desired version and if it fails, delete and reclone.
try {
console.log(`Checking if a valid cloned repo exists with version ${githubRepoTag}...`)
execSync(`git checkout tags/${githubRepoTag}`, { cwd: cloneDirectory });
}
catch (error) {

// if directory exists, raise an error
if (fs.existsSync(cloneDirectory)) {
throw new Error(`Directory ${cloneDirectory} already exists and is not a valid clone of ${githubRepoUrl}. Please delete this directory or specify a different cloneDirectory.`);
}

// else, we clone and check out the version.

// Clone the repo
console.log(`Cloning ${githubRepoUrl} into ${cloneDirectory}...`)
execSync(`git clone ${githubRepoUrl} ${cloneDirectory}`);

// Check out the desired version
console.log(`Checking out version ${githubRepoTag}...`)
execSync(`git checkout tags/${githubRepoTag}`, { cwd: cloneDirectory });

}

// Install the dependencies and build the application
console.log(`Installing dependencies`)
execSync('npm install', { cwd: cloneDirectory });

// Build the app with catalogUrl
console.log(`Building app with catalogUrl=${stacCatalogUrl} into ${cloneDirectory}`)
execSync(`npm run build -- --catalogUrl=${stacCatalogUrl}`, { cwd: cloneDirectory });

return './stac-browser/dist'

}


}

export interface StacBrowserProps {

/**
* Bucket ARN. If specified, the identity used to deploy the stack must have the appropriate permissions to create a deployment for this bucket.
* In addition, if specified, `cloudFrontDistributionArn` is ignored since the policy of an imported resource can't be modified.
*
* @default - No bucket ARN. A new bucket will be created.
*/

readonly bucketArn?: string;

/**
* STAC catalog URL
*/
readonly stacCatalogUrl: string;

/**
* Tag of the radiant earth stac-browser repo to use to build the app.
*/
readonly githubRepoTag: string;


/**
* The ARN of the cloudfront distribution that will be added to the bucket policy with read access.
* If `bucketArn` is specified, this parameter is ignored since the policy of an imported bucket can't be modified.
*
* @default - No cloudfront distribution ARN. The bucket policy will not be modified.
*/
readonly cloudFrontDistributionArn?: string;

/**
* The name of the index document (e.g. "index.html") for the website. Enables static website
* hosting for this bucket.
*
* @default - No index document.
*/
readonly websiteIndexDocument?: string;

/**
* Location in the filesystem where to compile the browser code.
*
* @default - DEFAULT_CLONE_DIRECTORY
*/
readonly cloneDirectory?: string;

}
Loading