From b0d671b07a67a015775dcc8cf6d6656c0d35ca57 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 12 Apr 2021 11:14:33 -0700 Subject: [PATCH] Add additional configuration variable includeWWW (default=true) which will trigger additional resources to be created in order to support a www subdomain in addition to the root target domain --- aws-ts-static-website/.gitignore | 1 + aws-ts-static-website/Pulumi.yaml | 3 ++ aws-ts-static-website/index.ts | 62 ++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 aws-ts-static-website/.gitignore diff --git a/aws-ts-static-website/.gitignore b/aws-ts-static-website/.gitignore new file mode 100644 index 000000000..a4d503387 --- /dev/null +++ b/aws-ts-static-website/.gitignore @@ -0,0 +1 @@ +Pulumi.ts3.yaml \ No newline at end of file diff --git a/aws-ts-static-website/Pulumi.yaml b/aws-ts-static-website/Pulumi.yaml index 017cc73b1..b6e6afe6b 100644 --- a/aws-ts-static-website/Pulumi.yaml +++ b/aws-ts-static-website/Pulumi.yaml @@ -12,3 +12,6 @@ template: description: Relative path to the website's contents (e.g. the `./www` folder) static-website:certificateArn: description: (Optional) ACM certificate ARN for the target domain; must be in the us-east-1 region. If omitted, a certificate will be created. + static-website:includeWWW: + description: If true create an A record for the www subdomain of targetDomain pointing to the generated cloudfront distribution. If a certificate was generated it will support this subdomain. + default: true diff --git a/aws-ts-static-website/index.ts b/aws-ts-static-website/index.ts index 54c3efdc2..02890a383 100644 --- a/aws-ts-static-website/index.ts +++ b/aws-ts-static-website/index.ts @@ -11,6 +11,7 @@ import * as path from "path"; // so that different Pulumi Stacks can be brought up using the same code. const stackConfig = new pulumi.Config("static-website"); + const config = { // pathToWebsiteContents is a relativepath to the website's contents. pathToWebsiteContents: stackConfig.require("pathToWebsiteContents"), @@ -18,6 +19,10 @@ const config = { targetDomain: stackConfig.require("targetDomain"), // (Optional) ACM certificate ARN for the target domain; must be in the us-east-1 region. If omitted, an ACM certificate will be created. certificateArn: stackConfig.get("certificateArn"), + // If true create an A record for the www subdomain of targetDomain pointing to the generated cloudfront distribution. + // If a certificate was generated it will support this subdomain. + // default: true + includeWWW: stackConfig.getBoolean("includeWWW") || true }; // contentBucket is the S3 bucket that the website's contents will be stored in. @@ -92,10 +97,14 @@ if (config.certificateArn === undefined) { region: "us-east-1", // Per AWS, ACM certificate must be in the us-east-1 region. }); - const certificate = new aws.acm.Certificate("certificate", { + // if config.includeWWW include required subjectAlternativeNames to support the www subdomain + let certificateConfig:aws.acm.CertificateArgs = { domainName: config.targetDomain, - validationMethod: "DNS", - }, { provider: eastRegion }); + validationMethod: "DNS", + subjectAlternativeNames: config.includeWWW ? [`www.${config.targetDomain}`]: [] + } + + const certificate = new aws.acm.Certificate("certificate", certificateConfig, { provider: eastRegion }); const domainParts = getDomainAndSubdomain(config.targetDomain); const hostedZoneId = aws.route53.getZone({ name: domainParts.parentDomain }, { async: true }).then(zone => zone.zoneId); @@ -112,6 +121,22 @@ if (config.certificateArn === undefined) { ttl: tenMinutes, }); + // if config.includeWWW ensure we validate the www subdomain as well + var subdomainCertificateValidationDomain; + if (config.includeWWW) { + subdomainCertificateValidationDomain = new aws.route53.Record(`${config.targetDomain}-validation2`, { + name: certificate.domainValidationOptions[1].resourceRecordName, + zoneId: hostedZoneId, + type: certificate.domainValidationOptions[1].resourceRecordType, + records: [certificate.domainValidationOptions[1].resourceRecordValue], + ttl: tenMinutes, + }); + } + + // if config.includeWWW include the validation record for the www subdomain + const validationRecordFqdns = subdomainCertificateValidationDomain === undefined ? + [certificateValidationDomain.fqdn] : [certificateValidationDomain.fqdn, subdomainCertificateValidationDomain.fqdn] + /** * This is a _special_ resource that waits for ACM to complete validation via the DNS record * checking for a status of "ISSUED" on the certificate itself. No actual resources are @@ -123,12 +148,15 @@ if (config.certificateArn === undefined) { */ const certificateValidation = new aws.acm.CertificateValidation("certificateValidation", { certificateArn: certificate.arn, - validationRecordFqdns: [certificateValidationDomain.fqdn], + validationRecordFqdns: validationRecordFqdns, }, { provider: eastRegion }); certificateArn = certificateValidation.certificateArn; } +// if config.includeWWW include an alias for the www subdomain +const distributionAliases = config.includeWWW ? [config.targetDomain, `www.${config.targetDomain}`]:[config.targetDomain] + // distributionArgs configures the CloudFront distribution. Relevant documentation: // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html // https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html @@ -136,7 +164,7 @@ const distributionArgs: aws.cloudfront.DistributionArgs = { enabled: true, // Alternate aliases the CloudFront distribution can be reached at, in addition to https://xxxx.cloudfront.net. // Required if you want to access the distribution via config.targetDomain as well. - aliases: [ config.targetDomain ], + aliases: distributionAliases, // We only specify one origin for this distribution, the S3 content bucket. origins: [ @@ -247,7 +275,31 @@ function createAliasRecord( }); } +function createWWWAliasRecord(targetDomain: string, distribution: aws.cloudfront.Distribution): aws.route53.Record { + const domainParts = getDomainAndSubdomain(targetDomain); + const hostedZoneId = aws.route53.getZone({ name: domainParts.parentDomain }, { async: true }).then(zone => zone.zoneId); + + return new aws.route53.Record( + `${targetDomain}-www-alias`, + { + name: `www.${targetDomain}`, + zoneId: hostedZoneId, + type: "A", + aliases: [ + { + name: distribution.domainName, + zoneId: distribution.hostedZoneId, + evaluateTargetHealth: true, + }, + ], + } + ) +} + const aRecord = createAliasRecord(config.targetDomain, cdn); +if (config.includeWWW){ + const cnameRecord = createWWWAliasRecord(config.targetDomain, cdn); +} // Export properties from this stack. This prints them at the end of `pulumi up` and // makes them easier to access from the pulumi.com.