summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenny Ballou <kballou@devnulllabs.io>2020-01-28 21:49:30 -0700
committerKenny Ballou <kballou@devnulllabs.io>2020-02-05 17:33:12 -0700
commit0eea4a0abfdbf1225abd148eac0a7f151c1144b3 (patch)
tree7572d62e5a9260c2b755d6c085769f2dcab5e8b0
parent1bb882edd5c1745d1a1bd4cc12e30fcbd8f81be9 (diff)
downloadkennyballou.com-0eea4a0abfdbf1225abd148eac0a7f151c1144b3.tar.gz
kennyballou.com-0eea4a0abfdbf1225abd148eac0a7f151c1144b3.tar.xz
code-{build,commit} auto build and deploy blog
Create codecommit and codebuild resources to store and build web/blog content. Add in a lambda function to trigger the builds automatically to futher automate deployment and publishing of content. Signed-off-by: Kenny Ballou <kballou@devnulllabs.io>
-rw-r--r--Makefile10
-rwxr-xr-xscm/ppag.scm50
-rw-r--r--stacks/blog.tpl150
-rw-r--r--stacks/codebuild-service-role.json.in49
-rw-r--r--stacks/codecommit-build-policy.json.in33
-rwxr-xr-xstacks/codecommit-build.py76
6 files changed, 367 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 2acd93f..5acf3c4 100644
--- a/Makefile
+++ b/Makefile
@@ -14,9 +14,11 @@ else
endif
SOURCE_TEMPLATES:=$(wildcard stacks/*.tpl)
+IN_SOURCES:= stacks/codecommit-build.py
SOURCE_PARAMS:=$(wildcard params/*.tpl)
TEMPLATES:=$(patsubst %.tpl, $(BUILD_DIR)/%.template, $(SOURCE_TEMPLATES))
STACK_PARAMS:=$(patsubst %.tpl, $(BUILD_DIR)/%.params, $(SOURCE_PARAMS))
+IN_OUTPUTS:=$(patsubst %,%.in, $(IN_SOURCES))
SOURCES:=$(shell find stacks/ -type f -name '*.in')
@@ -28,7 +30,7 @@ all: autogen/stack.def \
autogen/stack.def: autogen/stack.def.in
cat $^ > $@
-$(BUILD_DIR)/%.json: %.tpl $(SOURCES)
+$(BUILD_DIR)/%.json: %.tpl $(SOURCES) $(IN_OUTPUTS)
mkdir -p $(dir $@)
$(AG) --override-tpl=$< --definitions=autogen/stack.def > $@
@@ -38,6 +40,12 @@ $(BUILD_DIR)/%.template: $(BUILD_DIR)/%.json
$(BUILD_DIR)/%.params: $(BUILD_DIR)/%.json
$(JQ) '.' $< > $@
+%.in: %
+ echo "[+ autogen5 template +]" > $@
+ echo '{"Fn::Join": ["\n", [' >> $@
+ $(SHELL) scm/ppag.scm $^ >> $@
+ echo ']]}' >> $@
+
.PHONY: clean
clean:
-rm -r $(BUILD_DIR)
diff --git a/scm/ppag.scm b/scm/ppag.scm
new file mode 100755
index 0000000..e53ca1a
--- /dev/null
+++ b/scm/ppag.scm
@@ -0,0 +1,50 @@
+#!/usr/bin/env sh
+exec guile -e '(@ (ppag) main)' -s "${0}" "${@}"
+!#
+(define-module (ppag))
+
+(export main)
+
+(use-modules (ice-9 match))
+(use-modules (ice-9 pretty-print))
+(use-modules (ice-9 rdelim))
+
+(define (read-file filename)
+ "Read the entire contents of the provided file
+
+Reads the file line by line, accumulating the result into a list. Stops when
+reaching the EOF marker. `cons' will construct the list in reverse, reverse at
+the end.
+
+We assume the file provided exists and is readable."
+ (define (iter fh acc)
+ (let ((line (read-line fh)))
+ (if (eof-object? line)
+ acc
+ (iter fh (cons line acc)))))
+ (let ((fh (open-input-file filename)))
+ (reverse (iter fh '()))))
+
+(define (json-encode-line line)
+ "JSON encode the provided line."
+ (string-join (append '("\"") (map json-encode-char (string->list line)) '("\"")) ""))
+
+(define (json-encode-char char)
+ "JSON encode character
+
+We need to take special care around quotes and backslack characters."
+ (cond ((eq? char #\") "\"")
+ ((eq? char #\\) "\\")
+ (else
+ (list->string (list char)))))
+
+(define (json-encode-file filename)
+ "JSON encode the file, line by line"
+ (map json-encode-line (read-file filename)))
+
+(define (main args)
+ (match args
+ ((_ input-file)
+ (display (string-join (json-encode-file input-file) ",\n"))
+ (newline))
+ (_ (error "Unrecognized usage."))))
diff --git a/stacks/blog.tpl b/stacks/blog.tpl
index 454540c..3a6cd7f 100644
--- a/stacks/blog.tpl
+++ b/stacks/blog.tpl
@@ -230,6 +230,156 @@
"URIRewriteLambdaFunction", "Arn"]},
"Description": "Lambda Function performing URI rewriting"
}
+ },
+ "BlogContentRepository": {
+ "Type": "AWS::CodeCommit::Repository",
+ "Properties": {
+ "RepositoryDescription": "Blog Content Repository",
+ "RepositoryName": {"Ref": "BlogBucketName"},
+ "Triggers": [
+ {
+ "Name": "Build and Deploy",
+ "Branches": ["master"],
+ "DestinationArn": {"Ref": "CodeCommitEventsSnsTopic"},
+ "Events": ["all"]
+ }
+ ]
+ }
+ },
+ "BlogCodeBuildLogGroup": {
+ "Type": "AWS::Logs::LogGroup",
+ "Properties": {
+ "LogGroupName": {"Fn::Join": ["-", [
+ "/aws/codebuild/CodeBuild",
+ {"Ref": "BlogBucketName"}]]},
+ "RetentionInDays": 14
+ }
+ },
+ "BlogCodeBuild": {
+ "Type": "AWS::CodeBuild::Project",
+ "Properties": {
+ "Name": "BlogCI",
+ "Description": "Blog Build Project",
+ "Artifacts": {
+ "Type": "NO_ARTIFACTS"
+ },
+ "Environment": {
+ "ComputeType": "BUILD_GENERAL1_SMALL",
+ "Image": "kennyballou/debian-pandoc:latest",
+ "Type": "LINUX_CONTAINER"
+ },
+ "LogsConfig": {
+ "CloudWatchLogs": {
+ "GroupName": {"Fn::Join": ["-", [
+ "/aws/codebuild/CodeBuild",
+ {"Ref": "BlogBucketName"}
+ ]]},
+ "Status": "ENABLED"
+ }
+ },
+ "ServiceRole": {"Ref": "CodeBuildIamServiceRole"},
+ "Source": {
+ "Type": "CODECOMMIT",
+ "Location": {"Fn::GetAtt": ["BlogContentRepository",
+ "CloneUrlHttp"]}
+ }
+ }
+ },
+ "CodeCommitEventsSnsTopic": {
+ "Type": "AWS::SNS::Topic",
+ "Properties": {
+ "DisplayName": "CodeCommit Events",
+ "TopicName": "codecommit-events"
+ }
+ },
+ "CodeBuildIamManagedPolicy": {
+ "Type": "AWS::IAM::ManagedPolicy",
+ "Properties": {
+ "Description": "CodeBuild Service Policy",
+ "PolicyDocument": [+ INCLUDE "codebuild-service-role.json.in" +]
+ }
+ },
+ "CodeBuildIamServiceRole": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Principal": {
+ "Service": "codebuild.amazonaws.com"
+ },
+ "Effect": "Allow"
+ }
+ ]
+ },
+ "ManagedPolicyArns": [
+ {"Ref": "CodeBuildIamManagedPolicy"}
+ ]
+ }
+ },
+ "LambdaCodeCommitBuildIamManagedPolicy": {
+ "Type": "AWS::IAM::ManagedPolicy",
+ "Properties": {
+ "Description": "Lambda CodeCommit-Build Execution Policy",
+ "PolicyDocument": [+ INCLUDE "codecommit-build-policy.json.in" +]
+ }
+ },
+ "LambdaCodeCommitBuildIamServiceRole": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "AssumeRolePolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Action": "sts:AssumeRole",
+ "Principal": {
+ "Service": "lambda.amazonaws.com"
+ },
+ "Effect": "Allow"
+ }
+ ]
+ },
+ "ManagedPolicyArns": [
+ {"Ref": "LambdaCodeCommitBuildIamManagedPolicy"}
+ ]
+ }
+ },
+ "CodeCommitBuildLambdaPermission": {
+ "Type": "AWS::Lambda::Permission",
+ "Properties": {
+ "FunctionName": {"Fn::GetAtt": [
+ "CodeCommitBuildLambdaFunction", "Arn"]},
+ "Action": "lambda:InvokeFunction",
+ "Principal": "sns.amazonaws.com",
+ "SourceArn": {"Ref": "CodeCommitEventsSnsTopic"}
+ }
+ },
+ "CodeCommitBuildLambdaFunction": {
+ "Type": "AWS::Lambda::Function",
+ "Properties": {
+ "FunctionName": "codecommit-build-bae089e8-3871-4067-9a3d-bac114f08438",
+ "Code": {
+ "ZipFile": [+ INCLUDE "codecommit-build.py.in" +]
+ },
+ "Description": "Start builds on commit events",
+ "Handler": "index.handler",
+ "MemorySize": 128,
+ "Timeout": 3,
+ "Role": {"Fn::GetAtt": [
+ "LambdaCodeCommitBuildIamServiceRole", "Arn"]},
+ "Runtime": "python3.7"
+ }
+ },
+ "CodeCommitBuildSnsSubscription": {
+ "Type": "AWS::SNS::Subscription",
+ "Properties": {
+ "Protocol": "lambda",
+ "Endpoint": {"Fn::GetAtt": [
+ "CodeCommitBuildLambdaFunction", "Arn"]},
+ "TopicArn": {"Ref": "CodeCommitEventsSnsTopic"}
+ }
}
}
}
diff --git a/stacks/codebuild-service-role.json.in b/stacks/codebuild-service-role.json.in
new file mode 100644
index 0000000..abcb514
--- /dev/null
+++ b/stacks/codebuild-service-role.json.in
@@ -0,0 +1,49 @@
+[+ autogen5 template -*- mode: json -*- +]
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Resource": [
+ {"Fn::Join": [":", [
+ "arn:aws:logs",
+ {"Ref": "AWS::Region"},
+ {"Ref": "AWS::AccountId"},
+ "log-group:/aws/codebuild/CodeBuild*"]]},
+ {"Fn::Join": [":", [
+ "arn:aws:logs",
+ {"Ref": "AWS::Region"},
+ {"Ref": "AWS::AccountId"},
+ "log-group:/aws/codebuild/CodeBuild*",
+ "log-stream:*"]]}
+ ]
+ }, {
+ "Effect": "Allow",
+ "Action": [
+ "codecommit:GitPull"
+ ],
+ "Resource": [
+ {"Fn::Join": [":", [
+ "arn:aws:codecommit",
+ {"Ref": "AWS::Region"},
+ {"Ref": "AWS::AccountId"},
+ "*"]]}
+ ]
+ }, {
+ "Effect": "Allow",
+ "Action": [
+ "s3:PutObject",
+ "s3:Get*",
+ "s3:List"
+ ],
+ "Resource": [
+ {"Fn::GetAtt": ["BlogContentBucket", "Arn"]},
+ {"Fn::Join": ["", [{"Fn::GetAtt": ["BlogContentBucket", "Arn"]}, "/*"]]}
+ ]
+ }
+ ]
+}
diff --git a/stacks/codecommit-build-policy.json.in b/stacks/codecommit-build-policy.json.in
new file mode 100644
index 0000000..634acb2
--- /dev/null
+++ b/stacks/codecommit-build-policy.json.in
@@ -0,0 +1,33 @@
+[+ autogen5 template -*- mode: json -*- +]
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action": [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"
+ ],
+ "Resource": [
+ {"Fn::Join": [":", [
+ "arn:aws:logs",
+ {"Ref": "AWS::Region"},
+ {"Ref": "AWS::AccountId"},
+ "log-group:/aws/lambda/codecommit-build-bae089e8-3871-4067-9a3d-bac114f08438:*"
+ ]]}
+ ]
+ }, {
+ "Effect": "Allow",
+ "Action": [
+ "codebuild:StartBuild"
+ ],
+ "Resource": [
+ {"Fn::Join": [":", [
+ "arn:aws:codebuild",
+ {"Ref": "AWS::Region"},
+ {"Ref": "AWS::AccountId"},
+ "project/*"]]}
+ ]
+ }
+ ]
+}
diff --git a/stacks/codecommit-build.py b/stacks/codecommit-build.py
new file mode 100755
index 0000000..9c8d3ce
--- /dev/null
+++ b/stacks/codecommit-build.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+'''
+CodeCommit to CodeBuild Integration
+
+Start CodeBuild jobs based on CodeCommit events.
+'''
+
+
+import json
+import logging
+import boto3
+
+LOG = logging.getLogger()
+LOG.setLevel(logging.INFO)
+
+PROJECT_NAME = 'BlogCI'
+
+
+def get_is_tag(ref):
+ return 'tags' in ref
+
+
+def submit_build(reference, project_name, region):
+ '''Submit codebuild job for reference'''
+ client = boto3.client('codebuild', region_name=region)
+
+ response = client.start_build(
+ projectName=project_name,
+ sourceVersion=reference,
+ )
+ LOG.info(response['build'])
+
+
+def debug_handler(detail):
+ '''Log details and exit'''
+ LOG.warn(detail)
+ return True
+
+
+def reference_change_handler(detail):
+ '''handle reference change events, submitting code build jobs if needed'''
+ references = detail['codecommit']['references']
+ region = detail['awsRegion']
+ for reference in references:
+ is_deleted = reference.get('deleted', False)
+ if is_deleted:
+ continue
+ if 'tags' in reference['ref']:
+ submit_build(reference['ref'], PROJECT_NAME, region)
+ else:
+ submit_build(reference['commit'], PROJECT_NAME, region)
+
+
+EVENT_HANDLERS = {
+ 'TriggerEventTest': False,
+ 'ReferenceChanges': reference_change_handler,
+}
+
+
+def handler(event, _context):
+ '''Main Event Handler'''
+ for message_record in event['Records']:
+ records = json.loads(message_record['Sns']['Message'])
+ for record in records['Records']:
+ name = record['eventName']
+ event_handler = EVENT_HANDLERS.get(name)
+ if event_handler is False:
+ return True
+ elif event_handler is None:
+ event_handler = debug_handler
+ try:
+ event_handler(record)
+ except Exception as ex:
+ LOG.error('Runtime exception occurred: %s', ex)
+
+ return True