diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 5f4c28e327b65..ffa144a1cf2f9 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -384,6 +384,24 @@ export interface BucketProps { * @default No lifecycle rules */ lifecycleRules?: LifecycleRule[]; + + /** + * The name of the index document (e.g. "index.html") for the website. Enables static website + * hosting for this bucket. + */ + websiteIndexDocument?: string; + + /** + * The name of the error document (e.g. "404.html") for the website. + * `websiteIndexDocument` must also be set if this is set. + */ + websiteErrorDocument?: string; + + /** + * Grants public read access to all objects in the bucket. + * Similar to calling `bucket.grantPublicAccess()` + */ + publicReadAccess?: boolean; } /** @@ -414,6 +432,7 @@ export class Bucket extends BucketRef { bucketEncryption, versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined, lifecycleConfiguration: new cdk.Token(() => this.parseLifecycleConfiguration()), + websiteConfiguration: this.renderWebsiteConfiguration(props) }); cdk.applyRemovalPolicy(resource, props.removalPolicy); @@ -431,6 +450,10 @@ export class Bucket extends BucketRef { // defines a BucketNotifications construct. Notice that an actual resource will only // be added if there are notifications added, so we don't need to condition this. this.notifications = new BucketNotifications(this, 'Notifications', { bucket: this }); + + if (props.publicReadAccess) { + this.grantPublicAccess(); + } } /** @@ -598,6 +621,21 @@ export class Bucket extends BucketRef { })); } } + + private renderWebsiteConfiguration(props: BucketProps): cloudformation.BucketResource.WebsiteConfigurationProperty | undefined { + if (!props.websiteErrorDocument && !props.websiteIndexDocument) { + return undefined; + } + + if (props.websiteErrorDocument && !props.websiteIndexDocument) { + throw new Error(`"websiteIndexDocument" is required if "websiteErrorDocument" is set`); + } + + return { + indexDocument: props.websiteIndexDocument, + errorDocument: props.websiteErrorDocument + }; + } } /** diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 178e4cf051a6b..42a2c0dc85696 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -1143,5 +1143,43 @@ export = { })); test.done(); } + }, + + 'website configuration': { + 'only index doc'(test: Test) { + const stack = new cdk.Stack(); + new s3.Bucket(stack, 'Website', { + websiteIndexDocument: 'index2.html' + }); + expect(stack).to(haveResource('AWS::S3::Bucket', { + WebsiteConfiguration: { + IndexDocument: "index2.html" + } + })); + test.done(); + }, + 'fails if only error doc is specified'(test: Test) { + const stack = new cdk.Stack(); + test.throws(() => { + new s3.Bucket(stack, 'Website', { + websiteErrorDocument: 'error.html' + }); + }, /"websiteIndexDocument" is required if "websiteErrorDocument" is set/); + test.done(); + }, + 'error and index docs'(test: Test) { + const stack = new cdk.Stack(); + new s3.Bucket(stack, 'Website', { + websiteIndexDocument: 'index2.html', + websiteErrorDocument: 'error.html', + }); + expect(stack).to(haveResource('AWS::S3::Bucket', { + WebsiteConfiguration: { + IndexDocument: "index2.html", + ErrorDocument: "error.html" + } + })); + test.done(); + } } };