2019-01-17 21:59:44 +00:00
|
|
|
###############################################################################
|
|
|
|
### 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
|
|
|
|
2019-01-17 21:59:44 +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.
|
2019-01-17 21:59:44 +00:00
|
|
|
|
|
|
|
DomainName:
|
|
|
|
Type: String
|
2019-01-19 06:24:10 +00:00
|
|
|
Description: The docs website domain name.
|
2019-01-17 21:59:44 +00:00
|
|
|
Default: dev.docs.influxdata.com
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
Outputs:
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
DocsProdBucketArn:
|
|
|
|
Description: The ARN of the S3 bucket hosting the static content.
|
2020-08-27 05:19:33 +00:00
|
|
|
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
|
2019-01-17 21:59:44 +00:00
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
Resources:
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
DocsCloudFrontDistribution:
|
|
|
|
Type: AWS::CloudFront::Distribution
|
|
|
|
Properties:
|
|
|
|
DistributionConfig:
|
|
|
|
Aliases:
|
|
|
|
- !Ref DomainName
|
|
|
|
DefaultCacheBehavior:
|
2020-08-27 05:19:33 +00:00
|
|
|
TargetOriginId: !Ref DocsBucket
|
|
|
|
ViewerProtocolPolicy: redirect-to-https
|
|
|
|
DefaultTTL: 2592000
|
2019-01-17 21:59:44 +00:00
|
|
|
Compress: true
|
|
|
|
ForwardedValues:
|
|
|
|
QueryString: false
|
|
|
|
LambdaFunctionAssociations:
|
|
|
|
- EventType: origin-request
|
|
|
|
LambdaFunctionARN: !Ref DocsOriginRequestRewriteLambdaVersion
|
2020-08-27 05:19:33 +00:00
|
|
|
DefaultRootObject: '/'
|
2019-01-17 21:59:44 +00:00
|
|
|
CustomErrorResponses:
|
|
|
|
- ErrorCachingMinTTL: 300
|
|
|
|
ErrorCode: 403
|
|
|
|
ResponseCode: 404
|
|
|
|
ResponsePagePath: /404.html
|
|
|
|
Enabled: true
|
|
|
|
HttpVersion: http2
|
|
|
|
Origins:
|
2020-08-27 05:19:33 +00:00
|
|
|
- Id: !Ref DocsBucket
|
|
|
|
DomainName: !Join [ "", [ !Ref DocsBucket, ".s3.amazonaws.com" ] ]
|
2019-01-17 21:59:44 +00:00
|
|
|
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
|
2019-01-17 21:59:44 +00:00
|
|
|
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}'
|
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
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
|
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
DocsBucketPolicy:
|
2019-01-17 23:08:48 +00:00
|
|
|
Type: AWS::S3::BucketPolicy
|
|
|
|
Properties:
|
2020-08-27 05:19:33 +00:00
|
|
|
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
|
2020-08-27 05:19:33 +00:00
|
|
|
Resource: !Sub "arn:aws:s3:::${DocsBucket}/*"
|
2019-01-17 21:59:44 +00:00
|
|
|
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.
|
2019-01-17 21:59:44 +00:00
|
|
|
Code:
|
|
|
|
ZipFile: |
|
2019-06-25 15:07:48 +00:00
|
|
|
'use strict';
|
2019-01-18 20:17:30 +00:00
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
const path = require('path');
|
2019-06-25 15:07:48 +00:00
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
const latestVersions = {
|
|
|
|
'influxdb': 'v1.8',
|
|
|
|
'influxdbv2': 'v2.0',
|
|
|
|
'telegraf': 'v1.15',
|
|
|
|
'chronograf': 'v1.8',
|
|
|
|
'kapacitor': 'v1.5',
|
|
|
|
'enterprise': 'v1.8',
|
|
|
|
};
|
2019-06-25 15:07:48 +00:00
|
|
|
|
2020-09-01 23:10:28 +00:00
|
|
|
const archiveDomain = 'http://archive.docs.influxdata.com';
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
exports.handler = (event, context, callback) => {
|
|
|
|
|
|
|
|
function temporaryRedirect(condition, newUri) {
|
|
|
|
if (condition) {
|
|
|
|
return callback(null, {
|
2019-06-25 15:07:48 +00:00
|
|
|
status: '302',
|
|
|
|
statusDescription: 'Found',
|
|
|
|
headers: {
|
|
|
|
location: [{
|
|
|
|
key: 'Location',
|
2020-08-27 05:19:33 +00:00
|
|
|
value: newUri,
|
2019-06-25 15:07:48 +00:00
|
|
|
}],
|
|
|
|
}
|
|
|
|
});
|
2020-08-27 05:19:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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-06-25 15:07:48 +00:00
|
|
|
});
|
2020-08-27 05:19:33 +00:00
|
|
|
}
|
2019-01-18 20:17:30 +00:00
|
|
|
}
|
2019-01-18 22:28:40 +00:00
|
|
|
|
2020-08-27 05:19:33 +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,
|
|
|
|
};
|
2019-01-17 21:59:44 +00:00
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
// Remove index.html from path
|
2020-09-01 23:08:54 +00:00
|
|
|
permanantRedirect(request.uri.endsWith('index.html'), request.uri.substr(0, request.uri.length - indexPath.length));
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
// If file has a valid extension, return the request unchanged
|
|
|
|
if (validExtensions[parsedPath.ext]) {
|
|
|
|
callback(null, request);
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////// START PRODUCT-SPECIFIC REDIRECTS //////////////////////
|
|
|
|
|
|
|
|
//////////////////////////// v2 subdomain redirect ///////////////////////////
|
2020-09-01 23:08:54 +00:00
|
|
|
permanantRedirect(request.headers.host[0].value === 'v2.docs.influxdata.com', `https://docs.influxdata.com${request.uri}`);
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
////////////////////////// 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 //////////////////////////////
|
2020-09-01 23:08:54 +00:00
|
|
|
permanantRedirect(/^\/v2\.0\//.test(request.uri), request.uri.replace(/^\/v2\.0\//, `/influxdb/v2.0/`));
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
////////////////////////// Archive version redirects /////////////////////////
|
2020-09-01 23:08:54 +00:00
|
|
|
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}`);
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
/////////////////////// END PRODUCT-SPECIFIC REDIRECTS ///////////////////////
|
|
|
|
|
|
|
|
// Redirect to the a trailing slash
|
2020-09-01 23:08:54 +00:00
|
|
|
permanantRedirect(!request.uri.endsWith('/'), request.uri + '/');
|
2020-08-27 05:19:33 +00:00
|
|
|
|
|
|
|
// 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;
|
2019-01-17 21:59:44 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 05:19:33 +00:00
|
|
|
// 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);
|
2019-01-17 21:59:44 +00:00
|
|
|
};
|
2019-06-25 15:07:48 +00:00
|
|
|
|
2019-01-17 21:59:44 +00:00
|
|
|
Handler: index.handler
|
|
|
|
MemorySize: 128
|
|
|
|
Role: !Sub ${DocsOriginRequestRewriteLambdaRole.Arn}
|
2020-08-27 05:19:33 +00:00
|
|
|
Runtime: nodejs12.x
|
2019-01-17 21:59:44 +00:00
|
|
|
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
|
2019-01-17 21:59:44 +00:00
|
|
|
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
|
2019-01-25 17:54:53 +00:00
|
|
|
PolicyDocument:
|
2019-01-19 07:23:20 +00:00
|
|
|
Version: 2012-10-17
|
|
|
|
Statement:
|
|
|
|
- Effect: Allow
|
|
|
|
Action:
|
|
|
|
- s3:ListBucket
|
|
|
|
- s3:GetBucketLocation
|
|
|
|
Resource:
|
2020-08-27 05:19:33 +00:00
|
|
|
- !Sub "arn:aws:s3:::${DocsBucket}"
|
2019-01-19 07:23:20 +00:00
|
|
|
- Effect: Allow
|
|
|
|
Action:
|
|
|
|
- s3:PutObject
|
|
|
|
- s3:PutObjectAcl
|
|
|
|
- s3:DeleteObject
|
|
|
|
Resource:
|
2020-08-27 05:19:33 +00:00
|
|
|
- !Sub "arn:aws:s3:::${DocsBucket}/*"
|
2019-01-19 07:23:20 +00:00
|
|
|
- Effect: Allow
|
|
|
|
Action:
|
|
|
|
- cloudfront:GetDistribution
|
|
|
|
- cloudfront:CreateInvalidation
|
2019-01-25 17:54:53 +00:00
|
|
|
Resource: "*"
|
2019-01-19 06:24:10 +00:00
|
|
|
|
|
|
|
DocsCircleCIDeployAccessKey:
|
|
|
|
Type: AWS::IAM::AccessKey
|
|
|
|
Properties:
|
|
|
|
UserName: !Ref DocsCircleCIDeployUser
|