diff options
author | Kenny Ballou <kballou@devnulllabs.io> | 2020-01-28 21:49:30 -0700 |
---|---|---|
committer | Kenny Ballou <kballou@devnulllabs.io> | 2020-02-05 17:33:12 -0700 |
commit | 0eea4a0abfdbf1225abd148eac0a7f151c1144b3 (patch) | |
tree | 7572d62e5a9260c2b755d6c085769f2dcab5e8b0 | |
parent | 1bb882edd5c1745d1a1bd4cc12e30fcbd8f81be9 (diff) | |
download | kennyballou.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-- | Makefile | 10 | ||||
-rwxr-xr-x | scm/ppag.scm | 50 | ||||
-rw-r--r-- | stacks/blog.tpl | 150 | ||||
-rw-r--r-- | stacks/codebuild-service-role.json.in | 49 | ||||
-rw-r--r-- | stacks/codecommit-build-policy.json.in | 33 | ||||
-rwxr-xr-x | stacks/codecommit-build.py | 76 |
6 files changed, 367 insertions, 1 deletions
@@ -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 |