In our first post, we built the platform to host this blog. We then went on to publish our content. We’ve left ourselves with a couple of manual steps that we’ll now address.

Third build stage
The third step in building the blog (AWS icons from awsgeek.com)

We’ll be using AWS’s continuous integration service CodeBuild to automatically build our site and sync it to S3 whenever we push new content to GitHub.

Let’s start by creating our CodeBuild Project.

resource "aws_codebuild_project" "blog-builder" {
  name           = "blog-builder"
  description    = "blog-builder-${var.site_domain}"
  build_timeout  = "30"
  queued_timeout = "30"

  service_role  = aws_iam_role.codebuild_policy.arn

  artifacts {
    type = "NO_ARTIFACTS"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/standard:2.0"
    type                        = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"
  }

  source {
    type            = "GITHUB"
    location        = var.site_repo_link
    git_clone_depth = 1
    auth {
        type = "OAUTH"
    }
  }

  logs_config {
    cloudwatch_logs {
      group_name = "cloudbuild-log-group"
      stream_name = "cloudbuild-log-stream"
    }

    s3_logs {
      status = "ENABLED"
      location = "${aws_s3_bucket.codebuild_bucket.id}/build-log"
    }

  }
}

You may notice we’ve referenced a few new resources in the above. Let’s create them starting with the role.

resource "aws_iam_role" "codebuild_policy" {
  name = "codebuild_policy"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

Next, lets create the policy document.

data "aws_iam_policy_document" "codebuild_policy_doc" {
  statement {
    actions = [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
    ]
    resources = ["arn:aws:logs:*:*:*"]
    effect = "Allow"
  }
  statement {
    actions = ["s3:*"]
    resources = ["arn:aws:s3:::${aws_s3_bucket.site.id}/*", "arn:aws:s3:::${aws_s3_bucket.site.id}"]
    effect = "Allow"
  }
  statement {
    actions = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.codebuild_bucket.arn}/*"]
    effect = "Allow"
  }
}

Then attach the policy document to the role.

resource "aws_iam_role_policy" "codebuild_policy" {
  role = aws_iam_role.codebuild_policy.id
  policy = data.aws_iam_policy_document.codebuild_policy_doc.json
}

We’ve also defined a log bucket, so we’ll create that next.

resource "aws_s3_bucket" "codebuild_bucket" {
  bucket = "${var.site_domain}-codebuild"
  acl    = "private"

  lifecycle_rule {
    enabled = true

    expiration {
      days = 90
    }
  }
}

We also need to create a webhook. This will trigger a build whenever we push to our GitHub repository.

resource "aws_codebuild_webhook" "codebuild_webhook" {
  project_name = aws_codebuild_project.blog-builder.name

  filter_group {
    filter {
      type = "EVENT"
      pattern = "PUSH"
    }

    filter {
      type = "HEAD_REF"
      pattern = "master"
    }
  }
}

All that’s left is to add our content to the repository and include a build spec file. This file tells CodeBuild what to do. Our file (buildspec.yml) at the time of writing is as follows:

version: 0.1
   
phases:
  install:
    commands:
      - gem install jekyll bundler jekyll-watch jekyll-paginate jekyll-sitemap jekyll-gist jekyll-sass-converter jekyll-tagsgenerator jekyll-seo-tag jekyll-author-page
  build:
    commands:
      - echo "******** Building Jekyll site ********"
      - jekyll build
      - echo "******** Cleaning up files ********"
      - rm _site/buildspec.yml
      - rm _site/docker_buildspec.yml
      - rm _site/Changelog.md
      - echo "******** Uploading to S3 ********"
      - aws s3 sync _site/ s3://s3.martintyrer.com --delete

That’s it! We’ve successfully created the blog. From pushing a new post to it appearing on the blog takes just over three minutes. This is quite slow due to our CodeBuild install phase. In a future post we’ll look at reducing this by creating a custom build image.