Trigger a GitHub Pages rebuild with AWS Lambda and CloudWatch

GitHub Pages is pretty damn great.

I’ve been using it for years now but one thing has always bugged me. If you want
to schedule your posts in advance, they just never show up.

Why?

Because for posts to show up you have to trigger a rebuild of the project by
pushing a change.

Sure, you could leverage future: true in your _config.yml but then all of
your post-dated posts will show up.

Fine if for posts going out the next day but not ideal if you are trying to
schedule posts a week or so in advance.

Even though I am publishing new posts Monday, Wednesday and Friday, I am
actually doing the bulk of my writing on the Sunday before.

To avoid forgetting to make a post live, I did some research and coded up a
simple JavaScript / Node.js script to force a rebuild of my blog.

Said script is deployed to AWS as a Lambda function and I am leveraging
CloudWatch to schedule a rebuild every hour.

Sadly, it’s a bit too early for me to be able to tell how much this is going to
cost me. With that, after a few days of running it, I’m still clocking in at
$0.00 for both services.

Not saying it’s a free solution, but can’t imagine it’s going to top out at much
more than a few pennies.

Before we begin, you will need to setup a personal access token on GitHub. To do
so, just head over to GitHub’s settings page and set one up.

Also, and this is very important, you will need to create a file in your target
repository named .build. It can be empty but since this code relies on a
file’s existing SHA hash, you will need to create it manually first else you’re
run into a Missing SHA hash error.

To get started, create a new directory, cd into that bad boy and run yarn
init && yarn add request
to setup the project (creating package.json) and add
our only dependency.

Feel free to do this with npm if you’re into that.

Now that we have our dependencies all square, edit the file index.js and drop
this code in there. Nothing needs to be changed as we’ll be passing in
parameters via the command-line:

const request = require('request'

const username = process.env['GH_USER'
const project = process.env['GH_REPO'
const key = process.env['GH_KEY'

const file = `https://${username}:${key}@api.github.com/repos/${username}/${project}/contents/.build`

const start = (event, context, callback) => {
  request({
    headers: { 'User-Agent': username },
    method: 'GET',
    url: file,
  }, (getErr, getRes, getBody) => {
    if (getErr) return console.log('Error GETting File:', getErr

    const parsedGetBody = JSON.parse(getBody
    if (!parsedGetBody.sha) return console.log('Missing SHA hash'

    const { sha } = parsedGetBody

    request({
      headers: { 'User-Agent': username },
      method: 'PUT',
      url: file,
      body: JSON.stringify({
        message: 'Forcing rebuild',
        content: new Buffer(new Date().toString()).toString('base64'),
        sha,
      }),
    }, (putErr, putRes, putBody) => {
      if (putErr) return console.log('Error PUTting File:', putErr

      const parsedPutBody = JSON.parse(putBody

      console.log('Parsed PUT Body:', parsedPutBody
    
  


if (process.env['LAMBDA_TASK_ROOT']) {
  exports.handler = (event, context, callback) => {
    process.env['PATH'] += `:${process.env['LAMBDA_TASK_ROOT']}`
    start(event, context, callback
  }
} else {
  start({}, {}, (error, result) => {
    console.info(result
  
}

Save the file and let’s try running it!

To run this locally, your command will look like this:

GH_USER=joshtronic GH_REPO=joshtronic.com GH_KEY=secret node index.js

Be sure to swap out each of the parameters for your own information, else it’s
going to shit the bed!

Assuming everything worked, you could just configure the script to run
periodically from your local machine, or the server of your choosing.

If you’re like me and don’t want to maintain a server or run this locally, you
can proceed over to Amazon Web Services and setup a Lambda function and
CloudWatch to run it!

First thing to do out on Lambda is to create a new function (from scratch). Call
it what you want and configure the role as you see fit. I tend to create a
custom role for each of my functions and name it according to the name of the
function.

Since this code relies on an external dependency, you will need to zip up the
entire directory and upload that to AWS Lambda. Select Upload a .ZIP from the
Code entry type and proceed with uploading your .ZIP file of your project.

Quick note about the .ZIP file. You’ll want to zip the contents of your
project’s directory including the installed node_modules. Your files should be
in the root of the .ZIP file.

Once uploaded, you can try to run it, but it will error since we haven’t
configured the environment variables!

Expand the section labelled Environment variables and proceed to enter the
same parameters we passed to the script earlier, GH_USER, GH_REPO and
GH_KEY.

Once that’s all set, you can click Save and test in the header. Upon
completion it will hopefully say succeeded and show you the data that that
output from GitHub along with some summary data.

Keep in mind that sometimes it can throw a timeout error, happened to me while
testing things out. Just retry a moment later and hopefully the gremlins will
have flushed themselves out.

If you hop over to GitHub and browse the .build file in the target repository,
it should contain a date string that is around the time that you tested the
Lambda function!

This is all well and good, but we still need to get this running on a schedule.

From there, head over to CloudWatch and click on Rules (below Events) on the
sidebar. On the next page, click on Create rule which will take you to the
first step of the wizard.

Under Event Source click on Schedule and configure things how you’d like.
It’s probably overkill, but I went with hourly just in case something craps out
in the middle of the night. It’s probably save enough to run every 6-12 hours if
not daily. Your call.

Once the schedule is set, click on Add target* under Targets on the
right-hand side of the page. This reveals a form that defaults to the Lambda
function
option.

Below that, click on the Select function drop down and select the function we
just created. Click Configure details, give the rule a name on the next step
and click Create rule.

Donezo! Now you can schedule posts in the future and have them show up at the
right time (just like this post was 😉

Josh Sherman - The Man, The Myth, The Avatar

About Josh

Husband. Father. Pug dad. Musician. Founder of Holiday API, Head of Engineering and Emoji Specialist at Mailshake, and author of the best damn Lorem Ipsum Library for PHP.


If you found this article helpful, please consider buying me a coffee.