Joseph Van Boxtel

AWS S3 Deployment with Publish

I started my journey into building this website once John Sundell released the final package in his static site generation suite of tools, Publish. The first piece of infrastructure I wanted to get set up was automated deployment. I had already configured an Amazon AWS S3 bucket to host a static website. Then I just had to figure out how to hook up a custom deployment service to Publish. It turned out to be super easy.

To configure a deployment service for Publish just create an instance of DeploymentMethod and add it to your publishing pipeline with PublishingStep.deploy(using:) factory method. The initializer for DeploymentMethod is simple; it takes a name for the deployment and a closure to perform the deploy. It is recommended to create factory methods for reusable deployment methods so they can be added like this: .deploy(using: .awsS3()).

I initially tried using Amazon's iOS and Smoke SDKs but those are large dependencies that really made building and iteration slow. I settled on an approach that involved launching a shell to use aws2 command line tools. The command I used was:

aws2 s3 sync <<source>> s3:<<bucket>>/<<key>>

I had issues getting shellOut to find the aws2 executable so I just substituted the full path: /usr/local/bin/aws2. I tested the command with the --dryrun argument to ensure that I had the source and destination correct. Additionally, I wanted to use an IAM log in so I passed the --profile argument.

I created the profile using the AWS CLI like this:

aws2 configure --profile josephvb-website

It prompts for the access key, secret, and region. After that, the profile is ready to be used.

The plugin for Publish was simple to write once I had the AWS CLI working. I could have used the /Output folder to sync to S3 but Publish had a function createDeploymentFolder that seemed to be more in line with the design.

import Publish
import ShellOut

extension DeploymentMethod {
    static func awsS3(
        bucket: String = "josephvb.com", 
        s3Profile: String = "josephvb-website"
    ) -> Self {
        Self(name: "AWS-S3") { context in
            let deploymentFolder = try context.createDeploymentFolder(
                withPrefix: "AWS-S3-", configure: { _ in }
            )
            try shellOut(
                to: "aws2 s3 sync . s3:\(bucket) --profile \(s3Profile)",
                at: deploymentFolder.path,
                outputHandle: .standardOutput,
                errorHandle: .standardError
            )
        }
    }
}

Note that I used Self because DeploymentMethod is generic over the website.

Overall the final result is really satisfying. I successfully added a custom deployment for Publish and in very few lines of code.

Tagged with: