Skip to content

Commit

Permalink
feat: include DNS-validated ACM certificate and S3 bucket deployment …
Browse files Browse the repository at this point in the history
…for static site (#118)
  • Loading branch information
clareliguori authored and rix0rrr committed Sep 17, 2019
1 parent 34467c2 commit 3017879
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 38 deletions.
23 changes: 4 additions & 19 deletions typescript/static-site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,24 @@
>
> This examples does is built on Construct Libraries marked "Experimental" and may not be updated for latest breaking changes.
>
> If build is unsuccessful, please create an [issue](https://github.com/aws-samples/aws-cdk-examples/issues/new) so that we may debug the problem
> If build is unsuccessful, please create an [issue](https://github.com/aws-samples/aws-cdk-examples/issues/new) so that we may debug the problem
---
<!--END STABILITY BANNER-->

This example creates the infrastructure for a static site, which uses an S3 bucket for storing the content.
This example creates the infrastructure for a static site, which uses an S3 bucket for storing the content. The site contents (located in the 'site-contents' sub-directory) are deployed to the bucket.

The site redirects from HTTP to HTTPS, using a CloudFront distribution, Route53 alias record, and ACM certificate.

## Prep

The ACM certificate is expected to be created and validated outside of the CDK, with the certificate ARN stored in an AWS Systems Manager Parameter Store parameter.
The domain for the static site (i.e. mystaticsite.com) must be configured as a hosted zone in Route53 prior to deploying this example. For instructions on configuring Route53 as the DNS service for your domain, see the [Route53 documentation](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-configuring.html).

```
$ aws ssm put-parameter --region us-east-1 --name CertificateArn-www.mystaticsite.com --type String --value arn:aws:acm:...
```

## Deploy Infrastructure
## Deploy

```
$ npm install -g aws-cdk
$ npm install
$ npm run build
$ cdk deploy -c domain=mystaticsite.com -c subdomain=www
```

## Deploy Site Content

During the infrastructure deployment, you will see an output named something like "StaticSiteBucket34E5D9AF". The value is the bucket name where you can upload the static site content.

You will also see an output named something like "StaticSiteDistributionId8C64EF2A". This value is the distribution ID for the CloudFront distribution, which needs to be invalidated each time new content is uploaded to the bucket.

```
$ aws s3 sync . s3://$STATIC_SITE_BUCKET
$ aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*"
```
6 changes: 5 additions & 1 deletion typescript/static-site/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class MyStaticSiteStack extends cdk.Stack {

const app = new cdk.App();

new MyStaticSiteStack(app, 'MyStaticSite', { env: { region: 'us-east-1' } });
new MyStaticSiteStack(app, 'MyStaticSite', { env: {
// Stack must be in us-east-1, because the ACM certificate for a
// global CloudFront distribution must be requested in us-east-1.
region: 'us-east-1'
}});

app.synth();
1 change: 1 addition & 0 deletions typescript/static-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@aws-cdk/aws-route53": "*",
"@aws-cdk/aws-route53-targets": "*",
"@aws-cdk/aws-s3": "*",
"@aws-cdk/aws-s3-deployment": "*",
"@aws-cdk/core": "*",
"aws-cdk": "*"
}
Expand Down
6 changes: 6 additions & 0 deletions typescript/static-site/site-contents/error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<header><title>Hello world: Error</title></header>
<body>
Uh oh, you reached the error page!
</body>
</html>
6 changes: 6 additions & 0 deletions typescript/static-site/site-contents/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<header><title>Hello world</title></header>
<body>
Hello, world!
</body>
</html>
38 changes: 20 additions & 18 deletions typescript/static-site/static-site.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env node
import cloudfront = require('@aws-cdk/aws-cloudfront');
import route53 = require('@aws-cdk/aws-route53');
import route53 = require('@aws-cdk/aws-route53');
import s3 = require('@aws-cdk/aws-s3');
import s3deploy = require('@aws-cdk/aws-s3-deployment');
import acm = require('@aws-cdk/aws-certificatemanager');
import cdk = require('@aws-cdk/core');
import targets = require('@aws-cdk/aws-route53-targets/lib');
import targets = require('@aws-cdk/aws-route53-targets/lib');
import { Construct } from '@aws-cdk/core';

export interface StaticSiteProps {
Expand All @@ -13,19 +14,18 @@ export interface StaticSiteProps {
}

/**
* Static site infrastructure, which uses an S3 bucket for the content.
* Static site infrastructure, which deploys site content to an S3 bucket.
*
* The site redirects from HTTP to HTTPS, using a CloudFront distribution,
* Route53 alias record, and ACM certificate.
*
* The ACM certificate is expected to be created and validated outside of the CDK,
* with the certificate ARN stored in an SSM Parameter.
*/
export class StaticSite extends Construct {
constructor(parent: Construct, name: string, props: StaticSiteProps) {
super(parent, name);

const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: props.domainName });
const siteDomain = props.siteSubDomain + '.' + props.domainName;
new cdk.CfnOutput(this, 'Site', { value: 'https://' + siteDomain });

// Content bucket
const siteBucket = new s3.Bucket(this, 'SiteBucket', {
Expand All @@ -35,16 +35,18 @@ export class StaticSite extends Construct {
publicReadAccess: true,

// The default removal policy is RETAIN, which means that cdk destroy will not attempt to delete
// the new bucket, and it will remain in your account until manually deleted. By setting the policy to
// the new bucket, and it will remain in your account until manually deleted. By setting the policy to
// DESTROY, cdk destroy will attempt to delete the bucket, but will error if the bucket is not empty.
removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
});
new cdk.CfnOutput(this, 'Bucket', { value: siteBucket.bucketName });

// Pre-existing ACM certificate, with the ARN stored in an SSM Parameter
const certificateArn = new acm.Certificate(this, 'ArnParameter', {
domainName: props.domainName
// TLS certificate
const certificateArn = new acm.DnsValidatedCertificate(this, 'SiteCertificate', {
domainName: siteDomain,
hostedZone: zone
}).certificateArn;
new cdk.CfnOutput(this, 'Certificate', { value: certificateArn });

// CloudFront distribution that provides HTTPS
const distribution = new cloudfront.CloudFrontWebDistribution(this, 'SiteDistribution', {
Expand All @@ -65,19 +67,19 @@ export class StaticSite extends Construct {
});
new cdk.CfnOutput(this, 'DistributionId', { value: distribution.distributionId });

/**
* NOTE: the code below is not transpiling properly to JavaScript
* Pending review by AWS team
*/

// Route53 alias record for the CloudFront distribution
const zone = new route53.HostedZone(this, 'MyHostedZone', {
zoneName: props.domainName
});
new route53.ARecord(this, 'SiteAliasRecord', {
recordName: siteDomain,
target: route53.AddressRecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
zone
});

// Deploy site contents to S3 bucket
new s3deploy.BucketDeployment(this, 'DeployWithInvalidation', {
source: s3deploy.Source.asset('./site-contents'),
destinationBucket: siteBucket,
distribution,
distributionPaths: ['/*'],
});
}
}

0 comments on commit 3017879

Please sign in to comment.