docs-v2/deploy/docs-website.yml

338 lines
14 KiB
YAML
Raw Normal View History

###############################################################################
### 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.
2019-01-17 23:08:48 +00:00
###############################################################################
Parameters:
###############################################################################
AcmCertificateArn:
Type: String
Description: >
2019-01-19 06:24:10 +00:00
The ARN of the SSL certificate to use for the CloudFront
distribution.
DomainName:
Type: String
2019-01-19 06:24:10 +00:00
Description: The docs website domain name.
Default: dev.docs.influxdata.com
###############################################################################
Outputs:
###############################################################################
DocsProdBucketArn:
Description: The ARN of the S3 bucket hosting the static content.
Value: !GetAtt DocsBucket.Arn
2019-01-19 06:24:10 +00:00
DocsCircleCIDeployAccessKeyId:
Description: The access key ID for CircleCI deployment to S3.
Value: !Ref DocsCircleCIDeployAccessKey
DocsCircleCIDeploySecretAccessKey:
Description: The secret access key for CircleCI deployment to S3.
Value: !GetAtt DocsCircleCIDeployAccessKey.SecretAccessKey
###############################################################################
Resources:
###############################################################################
DocsCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- !Ref DomainName
DefaultCacheBehavior:
TargetOriginId: !Ref DocsBucket
ViewerProtocolPolicy: redirect-to-https
DefaultTTL: 2592000
Compress: true
ForwardedValues:
QueryString: false
LambdaFunctionAssociations:
- EventType: origin-request
LambdaFunctionARN: !Ref DocsOriginRequestRewriteLambdaVersion
DefaultRootObject: '/'
CustomErrorResponses:
- ErrorCachingMinTTL: 300
ErrorCode: 403
ResponseCode: 404
ResponsePagePath: /404.html
Enabled: true
HttpVersion: http2
Origins:
- Id: !Ref DocsBucket
DomainName: !Join [ "", [ !Ref DocsBucket, ".s3.amazonaws.com" ] ]
S3OriginConfig:
OriginAccessIdentity:
2019-01-19 06:24:10 +00:00
!Sub "origin-access-identity/cloudfront/${DocsCloudFrontOriginAccessIdentity}"
2019-01-18 23:12:01 +00:00
PriceClass: PriceClass_100
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}'
DocsBucket:
2019-01-17 23:08:48 +00:00
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
-
ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
Tags:
- Key: Domain
Value: !Ref DomainName
DocsBucketPolicy:
2019-01-17 23:08:48 +00:00
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref DocsBucket
2019-01-17 23:08:48 +00:00
PolicyDocument:
Statement:
-
2019-01-19 07:23:20 +00:00
Effect: Allow
2019-01-17 23:08:48 +00:00
Action:
- s3:GetObject
Resource: !Sub "arn:aws:s3:::${DocsBucket}/*"
Principal:
CanonicalUser: !GetAtt DocsCloudFrontOriginAccessIdentity.S3CanonicalUserId
DocsOriginRequestRewriteLambda:
Type: AWS::Lambda::Function
Properties:
2019-01-19 06:24:10 +00:00
Description: Lambda function performing request URI rewriting.
Code:
ZipFile: |
'use strict';
2019-01-18 20:17:30 +00:00
const path = require('path');
const latestVersions = {
'influxdb': 'v1.8',
'influxdbv2': 'v2.0',
'telegraf': 'v1.15',
'chronograf': 'v1.8',
'kapacitor': 'v1.5',
'enterprise': 'v1.8',
};
const archiveDomain = 'http://archive.docs.influxdata.com';
exports.handler = (event, context, callback) => {
function temporaryRedirect(condition, newUri) {
if (condition) {
return callback(null, {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: newUri,
}],
}
});
}
}
function permanantRedirect(condition, newUri) {
if (condition) {
return callback(null, {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
'location': [{
key: 'Location',
value: newUri,
}],
'cache-control': [{
key: 'Cache-Control',
value: "max-age=3600"
}],
},
});
}
2019-01-18 20:17:30 +00:00
}
const { request } = event.Records[0].cf;
const parsedPath = path.parse(request.uri);
const indexPath = 'index.html';
const validExtensions = {
'.html': true,
'.css': true,
'.js': true,
'.xml': true,
'.png': true,
'.gif': true,
'.jpg': true,
'.ico': true,
'.svg': true,
'.csv': true,
'.txt': true,
'.lp': true,
'.json': true,
'.rb': true,
'.eot': true,
'.ttf': true,
'.woff': true,
'.otf': true,
};
// Remove index.html from path
permanantRedirect(request.uri.endsWith('index.html'), request.uri.substr(0, request.uri.length - indexPath.length));
// If file has a valid extension, return the request unchanged
if (validExtensions[parsedPath.ext]) {
callback(null, request);
}
////////////////////// START PRODUCT-SPECIFIC REDIRECTS //////////////////////
//////////////////////////// v2 subdomain redirect ///////////////////////////
permanantRedirect(request.headers.host[0].value === 'v2.docs.influxdata.com', `https://docs.influxdata.com${request.uri}`);
////////////////////////// Latest version redirects //////////////////////////
temporaryRedirect(/\/influxdb\/latest/.test(request.uri), request.uri.replace(/\/latest/, `/${latestVersions['influxdb']}`));
temporaryRedirect(/\/telegraf\/latest/.test(request.uri), request.uri.replace(/\/latest/, `/${latestVersions['telegraf']}`));
temporaryRedirect(/\/chronograf\/latest/.test(request.uri), request.uri.replace(/\/latest/, `/${latestVersions['chronograf']}`));
temporaryRedirect(/\/kapacitor\/latest/.test(request.uri), request.uri.replace(/\/latest/, `/${latestVersions['kapacitor']}`));
temporaryRedirect(/\/enterprise_influxdb\/latest/.test(request.uri), request.uri.replace(/\/latest/, `/${latestVersions['enterprise']}`));
////////////////////////// Versionless URL redirects /////////////////////////
temporaryRedirect(request.uri === '/influxdb/', `/influxdb/${latestVersions['influxdb']}/`);
temporaryRedirect(request.uri === '/telegraf/', `/telegraf/${latestVersions['telegraf']}/`);
temporaryRedirect(request.uri === '/chronograf/', `/chronograf/${latestVersions['chronograf']}/`);
temporaryRedirect(request.uri === '/kapacitor/', `/kapacitor/${latestVersions['kapacitor']}/`);
temporaryRedirect(request.uri === '/enterprise_influxdb/', `/enterprise_influxdb/${latestVersions['enterprise']}/`);
/////////////////////////////// Flux redirects ///////////////////////////////
// Redirect flux guides and introduction based on latest InfluxDB version
if (/v2/.test(latestVersions['influxdb'])) {
temporaryRedirect(/\/flux\/v0\.[0-9]{1,2}\/guides\//.test(request.uri), request.uri.replace(/\/flux\/v0\.[0-9]{1,2}\/guides\//, `/influxdb/${latestVersions['influxdb']}/query-data/flux/`));
temporaryRedirect(/\/flux\/v0\.[0-9]{1,2}\/guides\//.test(request.uri), request.uri.replace(/\/flux\/v0\.[0-9]{1,2}\/introduction\//, `/influxdb/${latestVersions['influxdb']}/query-data/get-started/`));
} else {
temporaryRedirect(/\/flux\/v0\.[0-9]{1,2}\/guides\//.test(request.uri), request.uri.replace(/\/flux\/v0\.[0-9]{1,2}\/guides\//, `/influxdb/${latestVersions['influxdb']}/flux/guides/`));
temporaryRedirect(/\/flux\/v0\.[0-9]{1,2}\/guides\//.test(request.uri), request.uri.replace(/\/flux\/v0\.[0-9]{1,2}\/introduction\//, `/influxdb/${latestVersions['influxdb']}/flux/introduction/`));
}
// Redirect Flux stdlib and language sections to v2 Flux docs
temporaryRedirect(/\/flux\/v0\.[0-9]{1,2}\/(?:functions|stdlib|language)\//.test(request.uri), request.uri.replace(/\/flux\/v0\.[0-9]{1,2}\//, `/influxdb/${latestVersions['influxdbv2']}/reference/flux/`));
// Redirect versionless and base version to v2 Flux docs
temporaryRedirect(/^\/flux\/(?:v0\.[0-9]{1,2}\/|)$/.test(request.uri), `/influxdb/${latestVersions['influxdbv2']}/reference/flux/`);
////////////////////////////// v2 path redirect //////////////////////////////
permanantRedirect(/^\/v2\.0\//.test(request.uri), request.uri.replace(/^\/v2\.0\//, `/influxdb/v2.0/`));
////////////////////////// Archive version redirects /////////////////////////
permanantRedirect(/\/influxdb\/(?:v0\.[0-9]{1,2}|v1\.[0-2])\//.test(request.uri), `${archiveDomain}${request.uri}`);
permanantRedirect(/\/telegraf\/(?:v0\.[0-9]{1,2}|v1\.[0-8])\//.test(request.uri), `${archiveDomain}${request.uri}`);
permanantRedirect(/\/chronograf\/(?:v0\.[0-9]{1,2}|v1\.[0-5])\//.test(request.uri), `${archiveDomain}${request.uri}`);
permanantRedirect(/\/kapacitor\/(?:v0\.[0-9]{1,2}|v1\.[0-3])\//.test(request.uri), `${archiveDomain}${request.uri}`);
permanantRedirect(/\/enterprise_influxdb\/v1\.[0-3]\//.test(request.uri), `${archiveDomain}${request.uri}`);
permanantRedirect(/\/enterprise_kapacitor\//.test(request.uri), `${archiveDomain}${request.uri}`);
/////////////////////// END PRODUCT-SPECIFIC REDIRECTS ///////////////////////
// Redirect to the a trailing slash
permanantRedirect(!request.uri.endsWith('/'), request.uri + '/');
// Use index.html if the path doesn't have an extension
// or if the version number is parsed as an extension.
let newUri;
if (parsedPath.ext === '' || /\.\d*/.test(parsedPath.ext)) {
newUri = path.join(parsedPath.dir, parsedPath.base, indexPath);
} else {
newUri = request.uri;
}
// Replace the received URI with the URI that includes the index page
request.uri = newUri;
// Return to CloudFront
// request.uri = request.uri + indexPath;
2019-01-18 20:17:30 +00:00
callback(null, request);
};
Handler: index.handler
MemorySize: 128
Role: !Sub ${DocsOriginRequestRewriteLambdaRole.Arn}
Runtime: nodejs12.x
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
2019-01-19 07:23:20 +00:00
Action: sts:AssumeRole
Principal:
Service:
- edgelambda.amazonaws.com
- lambda.amazonaws.com
ManagedPolicyArns:
2019-01-17 23:08:48 +00:00
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
2019-01-19 06:24:10 +00:00
DocsCircleCIDeployUser:
Type: AWS::IAM::User
Properties:
Policies:
2019-01-19 07:23:20 +00:00
- PolicyName: giveaccesstoupdatedocsbuckets
PolicyDocument:
2019-01-19 07:23:20 +00:00
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource:
- !Sub "arn:aws:s3:::${DocsBucket}"
2019-01-19 07:23:20 +00:00
- Effect: Allow
Action:
- s3:PutObject
- s3:PutObjectAcl
- s3:DeleteObject
Resource:
- !Sub "arn:aws:s3:::${DocsBucket}/*"
2019-01-19 07:23:20 +00:00
- Effect: Allow
Action:
- cloudfront:GetDistribution
- cloudfront:CreateInvalidation
Resource: "*"
2019-01-19 06:24:10 +00:00
DocsCircleCIDeployAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref DocsCircleCIDeployUser