summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenny Ballou <kballou@devnulllabs.io>2018-04-09 21:50:35 -0600
committerKenny Ballou <kballou@devnulllabs.io>2018-08-19 11:31:21 -0600
commit655ef46f50d95209c39da1aa26a2d1c520b77193 (patch)
tree97835230a553c98a93b187acb146c6f604d97301
parent23ac5f2751e92e71da00e35753259b44d1aad1ab (diff)
downloadkennyballou.com-655ef46f50d95209c39da1aa26a2d1c520b77193.tar.gz
kennyballou.com-655ef46f50d95209c39da1aa26a2d1c520b77193.tar.xz
Add cloudformation templates and tooling
Blag will be managed using cloudfront/S3/lambdaedge, specified via cloudformation. Signed-off-by: Kenny Ballou <kballou@devnulllabs.io>
-rw-r--r--.gitignore1
-rw-r--r--Makefile43
-rw-r--r--autogen/stack.def1
-rw-r--r--autogen/stack.def.in1
-rw-r--r--params/blog.tpl1
-rw-r--r--stacks/blog.tpl233
-rw-r--r--stacks/uri-rewrite.in13
-rwxr-xr-xtools/bin/deploy-stack.sh44
8 files changed, 337 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index a201305..7a20842 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
ssl
www
+_build
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2acd93f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,43 @@
+PROJROOT:=$(git rev-parse --show-toplevel)
+
+AG:=autogen
+JQ:=jq
+JQ_OPTIONS:=
+REGION?=us-east-1
+BUILD_DIR:=_build/$(REGION)
+VERBOSE?=0
+
+ifeq ($(VERBOSE),1)
+ VFILTER :=
+else
+ VFILTER :=>/dev/null
+endif
+
+SOURCE_TEMPLATES:=$(wildcard stacks/*.tpl)
+SOURCE_PARAMS:=$(wildcard params/*.tpl)
+TEMPLATES:=$(patsubst %.tpl, $(BUILD_DIR)/%.template, $(SOURCE_TEMPLATES))
+STACK_PARAMS:=$(patsubst %.tpl, $(BUILD_DIR)/%.params, $(SOURCE_PARAMS))
+
+SOURCES:=$(shell find stacks/ -type f -name '*.in')
+
+.PHONY: all
+all: autogen/stack.def \
+ $(TEMPLATES) \
+ $(STACK_PARAMS)
+
+autogen/stack.def: autogen/stack.def.in
+ cat $^ > $@
+
+$(BUILD_DIR)/%.json: %.tpl $(SOURCES)
+ mkdir -p $(dir $@)
+ $(AG) --override-tpl=$< --definitions=autogen/stack.def > $@
+
+$(BUILD_DIR)/%.template: $(BUILD_DIR)/%.json
+ $(JQ) . $< > $@
+
+$(BUILD_DIR)/%.params: $(BUILD_DIR)/%.json
+ $(JQ) '.' $< > $@
+
+.PHONY: clean
+clean:
+ -rm -r $(BUILD_DIR)
diff --git a/autogen/stack.def b/autogen/stack.def
new file mode 100644
index 0000000..327cb03
--- /dev/null
+++ b/autogen/stack.def
@@ -0,0 +1 @@
+autogen definitions blog_kennyballou_com;
diff --git a/autogen/stack.def.in b/autogen/stack.def.in
new file mode 100644
index 0000000..327cb03
--- /dev/null
+++ b/autogen/stack.def.in
@@ -0,0 +1 @@
+autogen definitions blog_kennyballou_com;
diff --git a/params/blog.tpl b/params/blog.tpl
new file mode 100644
index 0000000..a261a8b
--- /dev/null
+++ b/params/blog.tpl
@@ -0,0 +1 @@
+[+ autogen5 template -*- mode: json -*- +]
diff --git a/stacks/blog.tpl b/stacks/blog.tpl
new file mode 100644
index 0000000..15bc7b4
--- /dev/null
+++ b/stacks/blog.tpl
@@ -0,0 +1,233 @@
+[+ autogen5 template -*- mode: json -*- +]
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Description": "Blog of kennyballou.com",
+ "Parameters": {
+ "DomainName": {
+ "Description": "Domain name of site",
+ "Type": "String",
+ "Default": "kennyballou.com"
+ },
+ "BlogBucketName": {
+ "Description": "Name of S3 Bucket",
+ "Type": "String",
+ "Default": "blog.kennyballou.com"
+ },
+ "CloudFrontHostedZone": {
+ "Description": "CloudFront Hosted Zone ID",
+ "Type": "String",
+ "Default": "Z2FDTNDATAQYW2"
+ }
+ },
+ "Resources": {
+ "HostedZone": {
+ "Type": "AWS::Route53::HostedZone",
+ "Properties": {
+ "Name": {"Ref": "DomainName"}
+ }
+ },
+ "BlogContentBucket": {
+ "Type": "AWS::S3::Bucket",
+ "Properties": {
+ "AccessControl": "Private",
+ "BucketName": {"Ref": "BlogBucketName"},
+ "LifecycleConfiguration": {
+ "Rules": [
+ {
+ "NoncurrentVersionExpirationInDays": 90,
+ "Status": "Enabled"
+ }
+ ]
+ },
+ "VersioningConfiguration": {
+ "Status": "Enabled"
+ },
+ "WebsiteConfiguration": {
+ "IndexDocument": "index.html",
+ "ErrorDocument": "404.html"
+ }
+ }
+ },
+ "BlogContentBucketPolicy": {
+ "Type": "AWS::S3::BucketPolicy",
+ "Properties": {
+ "Bucket": {"Ref": "BlogContentBucket"},
+ "PolicyDocument": {
+ "Statement": [
+ {
+ "Action": ["s3:GetObject"],
+ "Effect": "Allow",
+ "Resource": [
+ {"Fn::Join": ["/", [
+ {"Fn::GetAtt": [
+ "BlogContentBucket", "Arn"]},
+ "*"
+ ]]}
+ ],
+ "Principal": {
+ "CanonicalUser": {"Fn::GetAtt": [
+ "OriginAccessId",
+ "S3CanonicalUserId"]}
+ }
+ }
+ ]
+ }
+ }
+ },
+ "SSLCertificate": {
+ "Type": "AWS::CertificateManager::Certificate",
+ "Properties": {
+ "DomainName": {"Ref": "DomainName"}
+ }
+ },
+ "OriginAccessId": {
+ "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity",
+ "Properties": {
+ "CloudFrontOriginAccessIdentityConfig": {
+ "Comment": "S3 Bucket Access"
+ }
+ }
+ },
+ "CFDistribution": {
+ "Type": "AWS::CloudFront::Distribution",
+ "Properties": {
+ "DistributionConfig": {
+ "Aliases": [
+ {"Ref": "DomainName"}
+ ],
+ "DefaultRootObject": "index.html",
+ "Enabled": true,
+ "IPV6Enabled": true,
+ "HttpVersion": "http2",
+ "DefaultCacheBehavior": {
+ "TargetOriginId": {"Fn::Join": [".", [
+ "s3",
+ {"Ref": "BlogBucketName"}]]},
+ "ViewerProtocolPolicy": "redirect-to-https",
+ "MinTTL": 0,
+ "AllowedMethods": ["HEAD", "GET"],
+ "CachedMethods": ["HEAD", "GET"],
+ "ForwardedValues": {
+ "QueryString": true,
+ "Cookies": {
+ "Forward": "none"
+ }
+ },
+ "LambdaFunctionAssociations": [
+ {
+ "EventType": "origin-request",
+ "LambdaFunctionARN": {
+ "Ref": "URIRewriteLambdaVersion"
+ }
+ }
+ ]
+ },
+ "Origins": [
+ {
+ "S3OriginConfig": {
+ "OriginAccessIdentity": {"Fn::Join": ["/", [
+ "origin-access-identity/cloudfront",
+ {"Ref": "OriginAccessId"}
+ ]]}
+ },
+ "DomainName": {"Fn::Join": [".", [
+ {"Ref": "BlogBucketName"},
+ "s3.amazonaws.com"]]},
+ "Id": {"Fn::Join": [".", [
+ "s3",
+ {"Ref": "BlogBucketName"}]]}
+ }
+ ],
+ "PriceClass": "PriceClass_100",
+ "Restrictions": {
+ "GeoRestriction": {
+ "RestrictionType": "none",
+ "Locations": []
+ }
+ },
+ "ViewerCertificate": {
+ "SslSupportMethod": "sni-only",
+ "MinimumProtocolVersion": "TLSv1.1_2016",
+ "AcmCertificateArn": {"Ref": "SSLCertificate"}
+ }
+ }
+ }
+ },
+ "BlogAliasRecord": {
+ "Type": "AWS::Route53::RecordSet",
+ "Properties": {
+ "AliasTarget": {
+ "DNSName": {"Fn::GetAtt": ["CFDistribution", "DomainName"]},
+ "HostedZoneId": {"Ref": "CloudFrontHostedZone"}
+ },
+ "HostedZoneId": {"Ref": "HostedZone"},
+ "Name": {"Ref": "DomainName"},
+ "Type": "A"
+ }
+ },
+ "URIRewriteLambdaFunction": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "Description": "Lambda Function performing URI rewriting",
+ "Code": {
+ "ZipFile": [+ INCLUDE "uri-rewrite.in" +]
+ },
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Role": {"Fn::GetAtt": ["URIRewriteLambdaRole", "Arn"]},
+ "Runtime": "nodejs6.10",
+ "Tags": [
+ {"Key": "Domain", "Value": {"Ref": "DomainName"}}
+ ]
+ }
+ },
+ "URIRewriteLambdaRole": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": "sts:AssumeRole",
+ "Principal": {
+ "Service": [
+ "edgelambda.amazonaws.com",
+ "lambda.amazonaws.com"
+ ]
+ }
+ }
+ ]
+ },
+ "Policies": [
+ {
+ "PolicyName": "GrantCloudwatchLogAccess",
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Resource": [
+ "arn:aws:logs:*:*:*"
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "URIRewriteLambdaVersion": {
+ "Type": "AWS::Lambda::Version",
+ "Properties": {
+ "FunctionName": {"Ref": "URIRewriteLambdaFunction"},
+ "Description": "Lambda Function performing URI rewriting"
+ }
+ }
+ }
+}
diff --git a/stacks/uri-rewrite.in b/stacks/uri-rewrite.in
new file mode 100644
index 0000000..6df707c
--- /dev/null
+++ b/stacks/uri-rewrite.in
@@ -0,0 +1,13 @@
+[+ autogen5 template -*- mode: json -*- +]
+{"Fn::Join": ["\n", [
+ "'use strict';",
+ "exports.handler = (event, context, callback) => {",
+ " var whitelist = ['css', 'html', 'jpg', 'svg', 'png', 'txt', 'xml', 'pdf'];",
+ " var request = event.Records[0].cf.request;",
+ " var extension = request.uri.split('.').pop();",
+ " if (typeof extension == 'undefined' || !whitelist.includes(extension)) {",
+ " request.uri = request.uri.replace(/\\\/?$/, '\/index.html');",
+ " }",
+ " return callback(null, request);",
+ "};"
+]]}
diff --git a/tools/bin/deploy-stack.sh b/tools/bin/deploy-stack.sh
new file mode 100755
index 0000000..06faf80
--- /dev/null
+++ b/tools/bin/deploy-stack.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env sh
+
+set -ex
+
+STACK_NAME=blog-kennyballou
+REGION=us-east-1
+
+function deploy() {
+ aws cloudformation \
+ --region ${REGION} \
+ create-stack \
+ --stack-name ${STACK_NAME} \
+ --capabilities CAPABILITY_NAMED_IAM \
+ --template-body file://$(pwd)/_build/${REGION}/stacks/blog.template
+}
+
+function undeploy() {
+ aws cloudformation \
+ --region ${REGION} \
+ delete-stack \
+ --stack-name ${STACK_NAME}
+}
+
+function changeset() {
+ aws cloudformation \
+ --region ${REGION} \
+ create-change-set \
+ --stack-name ${STACK_NAME} \
+ --change-set-name ${STACK_NAME}-$(uuidgen) \
+ --capabilities CAPABILITY_NAMED_IAM \
+ --template-body file://$(pwd)/_build/${REGION}/stacks/blog.template
+}
+
+case $1 in
+ deploy)
+ deploy
+ ;;
+ changeset)
+ changeset
+ ;;
+ undeploy)
+ undeploy
+ ;;
+esac