A GitHub action to turn Jupyter notebooks into Minimal Mistakes Jekyll blog posts
Starting point
As mentioned in Turning Jupyter notebooks into Jekyll blog posts, I was deploying my Minimal Mistakes Jekyll site to the
gh_pages branch using a simple workflow. I now wanted to add an action which would take
all notebooks stored in a specific folder and turn them into markdown posts.
name: Deployment
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build-site:
runs-on: ubuntu-latest
steps:
# Check out current repo
- uses: actions/checkout@v2
# TODO HERE:
# Action which turns notebooks into Markdown posts
# Build and deploy the site
- name: Build and deploy
uses: EdricChan03/action-build-deploy-ghpages@v2.5.0
env:
JEKYLL_ENV: 'production'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
I had also written a simple script consisting of a few lines of bash and perl and a call to
jupyter nbconvert to convert notebooks to Markdown posts, taking care of moving the file
and the images and fixing image links. Finally, I found that creating a new layout type
notebook was the easiest way to include badges to view the notebook on GitHub or Google
Colab.
I decided to bundle the two files which are required on top of the typical Jekyll files into the action, such that it could be used with zero installation.
One seemingly small detail that doesn’t seem to be too clearly documented is how data can be accessed in the different stages of the action. Here is what I think to have understood:
- When setting up the docker container (when running the
Dockerfile), the files in the action repository are available. We can therefore copy any files into the container at that point. - The container will start in the ${GITHUB_WORKSPACE} which is mounted at
/github/workspace. The files in the action repository are not available anymore, unless we have copied them previously. - ${GITHUB_WORKSPACE} is also where, for example,
actions/checkoutis checking out the repository in. The workspace remains available throughout the steps of a job, which means that it can be used to store files which will be used by later actions.
Setting up a docker container
The action has relatively few requirements: We need an environment to run bash and perl and
we need to be able to use jupyter nbconvert. I decided to use jupyter/base-notebook, which
is a basic installation of Jupyter on Ubuntu. It is pretty light-weight but has all
the features we will need pre-installed.
The Dockerfile is relatively easy, we only need to specify our starting point (the current
base-notebook release), add the notebook layout and the action script, and set
the action script as the entry point.
# Container image that runs our code.
# base-notebook is a ubuntu image with a light-weight Jupyter installation
FROM jupyter/base-notebook:2021-12-16
USER root
# The action needs the special notebook layout and the action script
# We'll just copy the entire folder there
COPY . /action
# Run the action script when starting the docker container
ENTRYPOINT ["/action/convert_notebooks.sh"]
A few notes:
jupyter/base-notebookis released every day. For reproducibility, I picked a recent version. It’s unlikely I’ll have to change it any time soon.- By default, the
jupyter/base-notebookhas no root privileges. GitHub actions do, however, normally run using therootuser. The non-privileged default user does not have access to the ${GITHUB_WORKSPACE} folder which is the default work directory for GitHub actions. To play well with other actions, I decided to use therootuser rather than trying to work in a different folder. - When building the Docker container, we have full access to the files of the action. Since we
need some of them later, I decided to simply copy the full folder to the
/actionlocation in the container. - The entry point is the script which does the entire conversion. The container will be run from ${GITHUB_WORKSPACE}.
Action metadata file
The action metadata file defines the name and description of the action, as well as the run environment, the inputs, and the outputs. For our action, it is extremely easy since we need no inputs and no outputs. All we need to define is the name and the description, and that we are using docker to run it.
The minimal metadata file looks like this:
name: 'Jupyter Minimal Mistakes'
description: 'Convert Jupyter notebooks to Markdown blog posts ready for use with Jekyll and the Minimal Mistakes theme'
runs:
using: 'docker'
image: 'Dockerfile'
Action script
The action script itself is a simple bash script which does some sanity checks, and then
loops over the notebooks found in the _notebooks folder and creates post for each of
them. It also creates the notebook layout in the _layouts folder if it doesn’t exist.
The full action script is here.
Tests
I added some basic functionality and regression tests. The test checks out the current
repository and transforms a simple Jupyter notebook into Markdown. The resulting Markdown
file is checked against a reference file, and the existence of the notebook layout file
and the image file is verified. This is unlikely to catch all errors, but does at least
perform some basic functionality check.
Notes
To run a base image locally and play around a bit, this command is very helpful:
docker run -it --entrypoint /bin/bash jupyter/base-notebook
This will start a docker container with the chosen base image (in the example above
jupter/base-notebook) with a bash session as entry point. This is very handy to run some
commands interactively and make sure that everything works as intended.
Comments