############################################################################### ### AWS Cloudformation Template ### InfluxData Documentation Website Hosting and Deployment ############################################################################### AWSTemplateFormatVersion: 2010-09-09 Description: > Cloudformation template to stand up the AWS resources for hosting the InfluxData documentation static website created using Hugo. Cloudfront distribution is used to cache requests to an S3 bucket configured as a static website. A Lambda@Edge function rewrites requests with paths ending in index.html and requests to old v1 docs endpoints, which reside in a second bucket. Finally, a lambda is used to generate new versions of the docs using the GitHub source based on event and webhook triggers. ############################################################################### Parameters: ############################################################################### AcmCertificateArn: Type: String Description: > The ARN of the SSL certificate to use for the CloudFront distribution. DomainName: Type: String Description: The website domain name. Default: dev.docs.influxdata.com ############################################################################### Outputs: ############################################################################### DocsProdBucketArn: Description: The ARN of the S3 bucket hosting the static content. Value: !GetAtt DocsV2Bucket.Arn Export: Name: !Sub ${AWS::StackName}-bucket-arn ############################################################################### Resources: ############################################################################### DocsCloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Aliases: - !Ref DomainName DefaultCacheBehavior: Compress: true ForwardedValues: QueryString: false TargetOriginId: !Ref DocsV2Bucket ViewerProtocolPolicy: redirect-to-https LambdaFunctionAssociations: - EventType: origin-request LambdaFunctionARN: !Ref DocsOriginRequestRewriteLambdaVersion DefaultRootObject: index.html CustomErrorResponses: - ErrorCachingMinTTL: 300 ErrorCode: 403 ResponseCode: 404 ResponsePagePath: /404.html Enabled: true HttpVersion: http2 Origins: - DomainName: !Join [ "", [ !Ref DocsV2Bucket, ".s3.amazonaws.com" ] ] Id: !Ref DocsV2Bucket S3OriginConfig: OriginAccessIdentity: !Join [ "", [ "origin-access-identity/cloudfront/", !Ref DocsCloudFrontOriginAccessIdentity ] ] - DomainName: !Join [ "", [ !Ref DocsV1Bucket, ".s3.amazonaws.com" ] ] Id: !Ref DocsV1Bucket S3OriginConfig: OriginAccessIdentity: !Join [ "", [ "origin-access-identity/cloudfront/", !Ref DocsCloudFrontOriginAccessIdentity ] ] PriceClass: PriceClass_200 ViewerCertificate: AcmCertificateArn: !Ref AcmCertificateArn MinimumProtocolVersion: TLSv1.1_2016 SslSupportMethod: sni-only Tags: - Key: Domain Value: !Ref DomainName DocsCloudFrontOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: !Sub 'CloudFront Origin Access Identity for ${DomainName}' DocsV2Bucket: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 Tags: - Key: Domain Value: !Ref DomainName DocsV2BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref DocsV2Bucket PolicyDocument: Statement: - Action: - s3:GetObject Effect: Allow Resource: !Join [ "", [ "arn:aws:s3:::", !Ref DocsV2Bucket, "/*" ] ] Principal: CanonicalUser: !GetAtt DocsCloudFrontOriginAccessIdentity.S3CanonicalUserId DocsV1Bucket: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 Tags: - Key: Domain Value: !Ref DomainName DocsV1BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref DocsV1Bucket PolicyDocument: Statement: - Action: - s3:GetObject Effect: Allow Resource: !Join [ "", [ "arn:aws:s3:::", !Ref DocsV1Bucket, "/*" ] ] Principal: CanonicalUser: !GetAtt DocsCloudFrontOriginAccessIdentity.S3CanonicalUserId DocsOriginRequestRewriteLambda: Type: AWS::Lambda::Function Properties: Description: > Lambda function performing request URI rewriting. Code: ZipFile: | const config = { suffix: '.html', appendToDirs: 'index.html', removeTrailingSlash: false, }; const regexSuffixless = /\/[^/.]+$/; // e.g. "/some/page" but not "/", "/some/" or "/some.jpg" const regexTrailingSlash = /.+\/$/; // e.g. "/some/" or "/some/page/" but not root "/" exports.handler = function handler(event, context, callback) { const { request } = event.Records[0].cf; const { uri } = request; const { suffix, appendToDirs, removeTrailingSlash } = config; // Append ".html" to origin request if (suffix && uri.match(regexSuffixless)) { request.uri = uri + suffix; callback(null, request); return; } // Append "index.html" to origin request if (appendToDirs && uri.match(regexTrailingSlash)) { request.uri = uri + appendToDirs; callback(null, request); return; } // Redirect (301) non-root requests ending in "/" to URI without trailing slash if (removeTrailingSlash && uri.match(/.+\/$/)) { const response = { // body: '', // bodyEncoding: 'text', headers: { 'location': [{ key: 'Location', value: uri.slice(0, -1) }] }, status: '301', statusDescription: 'Moved Permanently' }; callback(null, response); return; } // If nothing matches, return request unchanged callback(null, request); }; Handler: index.handler MemorySize: 128 Role: !Sub ${DocsOriginRequestRewriteLambdaRole.Arn} Runtime: nodejs8.10 Tags: - Key: Domain Value: !Ref DomainName DocsOriginRequestRewriteLambdaVersion: Type: AWS::Lambda::Version Properties: FunctionName: !Ref DocsOriginRequestRewriteLambda Description: !Sub "URL rewriting for ${DomainName}" DocsOriginRequestRewriteLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - edgelambda.amazonaws.com - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole