diff --git a/typescript/static-site/README.md b/typescript/static-site/README.md index a9200bc7f..f7be4a7d1 100644 --- a/typescript/static-site/README.md +++ b/typescript/static-site/README.md @@ -8,24 +8,20 @@ > > 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 --- -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 @@ -33,14 +29,3 @@ $ 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 "/*" -``` \ No newline at end of file diff --git a/typescript/static-site/index.ts b/typescript/static-site/index.ts index 1f3b92b6e..efdcf4341 100644 --- a/typescript/static-site/index.ts +++ b/typescript/static-site/index.ts @@ -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(); diff --git a/typescript/static-site/package.json b/typescript/static-site/package.json index 9f9c826a1..796e263c9 100644 --- a/typescript/static-site/package.json +++ b/typescript/static-site/package.json @@ -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": "*" } diff --git a/typescript/static-site/site-contents/error.html b/typescript/static-site/site-contents/error.html new file mode 100644 index 000000000..0240c8288 --- /dev/null +++ b/typescript/static-site/site-contents/error.html @@ -0,0 +1,6 @@ + +
Hello world: Error
+ +Uh oh, you reached the error page! + + diff --git a/typescript/static-site/site-contents/index.html b/typescript/static-site/site-contents/index.html new file mode 100644 index 000000000..139707c92 --- /dev/null +++ b/typescript/static-site/site-contents/index.html @@ -0,0 +1,6 @@ + +
Hello world
+ +Hello, world! + + diff --git a/typescript/static-site/static-site.ts b/typescript/static-site/static-site.ts index 6f8378d59..60d6ee04a 100644 --- a/typescript/static-site/static-site.ts +++ b/typescript/static-site/static-site.ts @@ -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 { @@ -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', { @@ -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', { @@ -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: ['/*'], + }); } }