Resume as Code
I avoid brand new tech until I have an itch to scratch: a small, self-contained problem that seems it might lend itself nicely to $someTool. I'm usually unsure I'll be able to achieve what I have in mind. But maybe...
This time, the problem was something that had annoyed me for as long as I'd been employed: resumes. Not the fact of them, or even writing them, necessarily. Just managing different versions.
I was swimming in Dropbox folders with dozens of files saved in various formats; I needed to maintain multiple documents to preserve different iterations, and export PDFs for distribution. Version control was a nightmare.
LaTeX and git to the Rescue
One day I happened upon Daniel Cousineau's resume
. Daniel was using a tool I'd heard of but never worked with called LaTeX (a system for generating documents, and not a particularly new one), along with GitHub releases to track his resume's evolution. It seemed promising.
I set up alessbell/resume
, rewrote my resume as resume.tex
and, back in June, released v1.0
🎉
Wherefore Art GitHub Actions
Having a resume managed in GitHub was a very welcome change, but there were still a few manual steps every time I wanted to cut a release:
- Draft a new release via GitHub UI. Tag my commit and begin manually creating the release.
- Manually compile
resume.tex
and upload the PDF as a release asset. GitHub automatically includes the source code in both zip and tarball formats, but I wanted to include a compiledresume.pdf
, too. I'd runpdflatex
locally and drag and drop the file, again via GitHub UI. - Update the copy of
resume.pdf
in Gatsby's/static
folder. I'd take my new PDF and drag and drop it into my local copy of the codebase for this website, since I want aless.co/resume.pdf to always display the latest version. Then I'd manually commit it and open a PR.
Once I'd isolated the steps that were candidates for automation, I sketched out the ideal workflow: first, automating releases in alessbell/resume
, then somehow pinging another repository when a new release was published (?), and finally, the other repository (this blog) would download resume.pdf
from the latest release, commit it and open a PR... maybe?
I had no idea how feasible this all was, still knowing little to nothing about the GitHub Actions API. But automating even one step would be a win!
Spoiler Alert
tl;dr my ideal workflow was possible, so I built it 🐙💜
If you'd like to browse the code, steps 1 and 2 are achieved by the main workflow in alessbell/resume
. Step 3 is handled by actions in this blog's repository, namely /commit-resume
. For a walk-through of the code, keep reading 😎
1. Compile the PDF and Automate Releases
I figured automating the release part would be easy—surely there's already an action for that—but I wasn't so sure about compiling the LaTeX to PDF for inclusion in the release.
Delightfully, both steps turned out to be trivial to implement. I found off-the-shelf actions for both: xu-cheng/latex-action
for compiling my LaTeX to PDF and softprops/action-gh-release
for creating the GitHub release with the compiled PDF from the previous step attached as an asset.
My first workflow looked like this:
name: Publish new release of resume
on:
push:
tags:
- 'v*.*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Compile LaTeX document
uses: xu-cheng/latex-action@master
with:
root_file: resume.tex
- name: Release
uses: softprops/action-gh-release@v1
with:
files: resume.pdf
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
This workflow runs on any push
event targeting tags matching the glob pattern v*.*
(docs). The first step, actions/checkout
, checks out the repository to the current GitHub workspace so that the workflow can access the contents of your repository.
After that, I can pass the next step the name of my file, resume.tex
, and the subsequent step can directly access the compiled resume.pdf
from the previous one. In order to author a release, I need to pass in a GITHUB_TOKEN
; luckily GitHub already makes certain environment variables available.
2. Ping Another Repository
In order to keep the version of my resume displayed on this website up-to-date, I'd need a way to kick off a new workflow each time I published alessbell/resume
.
Luckily, such an event exists: repository_dispatch
triggers a webhook event "when you want to trigger a GitHub Actions workflow for activity that happens outside of GitHub," or, in this case, in a different repository's action. A simple curl request with the correct auth token, headers and body does the trick.
This is the whole bash script from alessbell/resume/ping-repo
:
#!/bin/bash
main() {
curl -XPOST -H "Accept: application/vnd.github.everest-preview+json" \
-H "Content-Type: application/json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
"https://api.github.com/repos/${REPO}/dispatches" \
--data '{"event_type": "update_resume"}'
}
main
There were two small caveats here. First, because this POST request is being dispatched for a repository other than the one from the action's execution context, I needed a personal access token with repository
scope set as a secret on the repository. I stored my secret as PA_TOKEN
and passed it in the way I had the others:
- name: Pings repo
uses: ./ping-repo
env:
GITHUB_TOKEN: ${{ secrets.PA_TOKEN }}
REPO: alessbell/aless.co
Second, certain events, e.g. push
will run on any branch unless the scope is narrowed by specifying a certain branch or tag. When I was testing this repository_dispatch
event, however, nothing was happening despite having pushed a workflow to a branch in my blog's repository listening for this exact dispatch event. It wasn't until I pushed the blog's workflow config to master that I saw it spring to life, activated by my Postman request to the /dispatches
endpoint.
3. Download New PDF, Open PR
This final step felt like a stretch goal, but it proved to be just enough work for a train ride from Rhode Island to NYC. The first part involves fetching the latest version of alessbell/resume
, and GitHub's API has a dedicated endpoint for retrieving information about a repository's latest release:
RELEASES_URL=https://api.github.com/repos/alessbell/resume/releases/latest
RES=$(curl -sSL -H "${AUTH_HEADER}" -H "${HEADER}" --user "${GITHUB_ACTOR}" -X GET ${RELEASES_URL})
VERSION=$(echo "${RES}" | jq --raw-output '.tag_name')
PDF_URL="https://github.com/alessbell/resume/releases/download/${VERSION}/resume.pdf"
# download resume.pdf and save in /static/resume.pdf
curl -L0 "${PDF_URL}" --output ./static/resume.pdf
Once I had the file downloaded, I'd just need to commit it and open a PR. This time, I'd try to use a pre-existing action with a bit less luck: I wasn't able to integrate vsoch/pull-request-action
directly (I'll be the first to say it could have been user error -- when I'm out of my comfort zone, I need to be able to tinker with the code), but reading its source taught me a lot about how to write a similar action that would work for my case.
I wound up with ~90 lines of bash and successfully used jq
to process JSON for the first time. There was plenty of trial and error along the way, but once I plugged it all together, it Just Worked.
Writing bash and yaml isn't part of my day job, but it was a lot of fun once I got started; tinkering with GitHub Actions was the perfect excuse to learn something new while scratching an itch.
Is there a workflow you're thinking about automating? I'd be curious to hear about it -- you can ping me on twitter at @alessbell or email me at web[at]bellisar.io.