Introduction
In our earlier article on Git pipelines, we mentioned that GitHub had released a beta of Actions, their latest CI/CD workflow automation tool. Let’s take a quick look at some of its features.
For simplicity, we’ll use the same example as in the previous article – that of rendering this article into HTML – which is more than enough to demonstrate the basic features.
To recap, the workflow for Git pipelines was:
- Get the latest commit in the repository
- Install GNU Make
- Install pandoc which is used to render Markdown into HTML
- Render the HTML document from Markdown
- Archive HTML document
The Actions based workflow is similar, but quite a bit simpler. It performs the following tasks:
- Get latest commit in the repository
- Render the HTML document from Markdown
- Publish the rendered HTML document to GitHub pages
It’s simpler, because we don’t need to install the dependent software – we can use pre-prepared Docker Hub images instead.
What are GitHub Actions?
Actions introduce integrated pipelines called workflows into a GitHub repository. That means we can access workflows directly from GitHub’s dashboard via the tab. (Note that when we were preparing this article, the job history in the Actions tab did not show until after we had published to the master
branch.)
From the Actions tab we can view job history as well as view, edit or add workflows:
What are GitHub Workflows?
Workflows define the automation steps of a pipeline. Workflows are stored in the .github/workflows
directory at the root of your project. A workflow has one or more jobs that contains a sequence of tasks called steps. As an example, lets work through this project’s workflow, which is defined in the yaml file below:
There are three core sections to a workflow (1) – (3):
(1) name
A workflow has a name. This name will appear as a title on the dashboard.
(2) on
This describes how this workflow gets triggered. There are multiple ways that a workflow can be triggered:
- on push or pull request on branch or tag
- on push or pull request on a path
- on a schedule
Here, we are experimenting with being triggered by a push on file changes to README.md
.
(3) jobs
Jobs contain steps for execution. The bulk of a jobs workflow appears under section (3). These are explained in sections (4) to (14) below.
(4) id
Jobs are given a unique id. Here, we have labelled it build
.
(5) name
Jobs have a name which will appear on GitHub.
(6) runs-on
Jobs are run on GitHub hosted virtual machine images. The current choices offer these three virtual environments types:
Ubuntu
Windows Server
macOS X
.
Apart from latest
there are a choice of versions for each virtual environment. The limitation here is that you must use one of these images. If you are invoking Docker based Actions, then you must use a Linux image. These Docker images also must run as root
, which could be problematic. For example, Haskell Stack will complain when installing dependencies with a user of different privileges.
(7) steps
The remainder of the job is composed of Steps. Steps are the workhorse of workflows. Steps can run set-up tasks, run commands or run actions. Our workflow performs three named tasks:
- shallow checkout
- render document
- publish to pages
(8) checkout
Previously with Azure pipelines we only needed to specify how the pipeline was triggered – it was assumed that the code was already checked out. With Actions this step is explicit: that is, we need to invoke an action to checkout from GitHub. The benefit is that you can finely tune how and what to checkout. In the example action, (8), we are performing a shallow checkout (depth of 1 commit) from the master branch.
(9) uses
To perform the checkout we are using the standard checkout action. We would recommend that you specify a specific version instead of a generic tag like @latest
.
When we were reviewing actions, it was helpful and instructive to view the source code to check whether the action provided the required features. For instance, we were able to trial three different actions to publish content, before settling on the current solution.
(10) with
Some actions require parameters. These are provide using the with
clause. In this case, (10) we are supplying specific checkout options.
Each Action can define its own values or defaults so it pays to read the source to determine the available choices for the specific version being used.
In other examples (11), we are overriding the default entry point of the Docker container, or specifying the directory location to publish, (12).
(11) using custom Docker
Custom Docker containers can be called as Actions. In this example we are calling a prepared image with all the tools used for rendering this project from markdown to HTML.
(12) publish pages
In our previous article we rendered markdown to HTML and provided it as an archive to download. A better solution is to publish static content to GitHub Pages. This required the creation of an access token which is nicely described here. This token is added to the project as a Settings > Secret named GH_PAGES_TOKEN
. This token is passed to the action so it is able to publish the rendered static HTML page to the gh_pages branch.
(13) if
If can conditionally execute a step. The conditional expression can be a Boolean expression or a GitHub context. If the condition is true, the step will execute. In our example it uses a context to check the status of the previous step.
(14) secrets
Secrets are encrypted environment variables. They are restricted for use in Actions. Here, we store the token required to publish to GitHub Pages. See (12).
Putting It All Together
We now have all the pieces in place to execute our workflow that will:
- Invoke an action to perform a shallow checkout of our repository from the
master
branch - Render the markdown using a custom Action from our own pandoc Docker container
- Use a public Action to publish the static HTML to GitHub pages
Workflows are integrated into GitHub unlike the previous Azure pipelines. A big relief!
Some Extras
Workflow Logs
Job runs are recorded. You can review a job by following the link from Workflow runs. This will show a run history like:
Each job step has logs that can be viewed and/or downloaded.
Editing a Workflow
GitHub provides an online editor for your workflow:
However, this editor does not currently validate the workflow. So, why is it even provided as it offers nothing that normal online editing doesn’t?
First Impressions
Our first impression of GitHub Actions is that they are a significant improvement over the former Azure pipelines. Features we particularly like are:
- Actions are well integrated into GitHub
- There’s an active marketplace for Actions and Apps. See a comparison between Actions and Apps here.
- Documentation is good
- The ability to use custom Docker images
- Fast workflows
However, there are also some drawbacks:
- There is no cache between jobs. The current recommended practice is to archive the required data, and then restore the archive on the required job. To do this you will require to execute Actions. Having a local cache is really important for projects like Java that have many dependencies. No cache means downloading each and every build!
- The recommended practice is to write actions in JavaScript since these actions are performed on the GitHub host, and do not need to be pulled from external sources. Really? JavaScript? It seems like a bizarre choice – JavaScript is not the first language DevOps would turn to when building workflow pipelines. Will GitHub Actions support other languages in the future?
We also found Docker Actions available on the marketplace are of variable quality. We spent time experimenting with different variations until we found those that matched our requirements. As the source code is available it was easy to evaluate an Actions implementation. Or, you could simply write your own following these instructions. We also found that we could use our existing Docker images without modification.
There are some good features to GitHub Actions which are easily composed. While JavaScript is not the first tool we would consider as a workflow language, Docker is very workable compromise, even with the small performance hit.
Resources
- Actions: Automating your workflow
- Actions: Creating a Docker Container
- Actions: Deploy Static Content to GitHub Pages
- Actions: Environment Variables
- Actions: GitHub Pages by CrazyMax
- Actions: Help
- Article: About Actions
- Article: Workflow Syntax for GitHub Actions
- GitHub Pages Basics
- Repository: github-actions
- Repository: git-pipelines