CI/CD with GitHub Actions

CI/CD with GitHub Actions

GitHub Actions enables GitHub users to define powerful workflows that run on Github. I saw potential to model a CI/CD workflow with GitHub actions. This blog post is about a general CI/CD setup with GitHub Actions journey.

Github Actions in a nutshell

Actions are currently in beta phase. Actions are basically a composition of docker container runs. Containers in Actions are referenced as:

  • Dockerfiles in GitHub repositories
  • Docker images in public Docker registries.

Please refer to Using a Docker image in an GitHub Action for more details.

To use an action, refer to the action in your .workflow file using a path relative to the repository directory e.g.

action "my action" {
  uses = "[using a path relative to the repository directory]"
}

This a specific example (based on shaking-finger-action):

action "post gif on fail" {
  uses = "jessfraz/shaking-finger-action@master"
  secrets = ["GITHUB_TOKEN"]
}

You can choose from a wide range of existing GitHub Actions or other listings like awesome actions. There are also icons that may help you to make your actions look pretty. A UI editor allows you to arrange actions as you can do with code as well.

My goal was to use Open Source actions as well as to define my own action. I used a golang hello world for my CI/CD github actions because I did not find too many Github Actions with golang examples.

A collection of actions is a workflow. The GitHub platform runs your workflows and actions. Workflows and subsequently actions are cron like scheduled or triggered by events. ForkEvent and PullRequestEvent are just two examples of a longer list of available events.
Actions have access to the payload of the event and a copy of the source code.

This is the Dockerfile I used for my CI/CD setup:

FROM golang:1.12.4

LABEL "inspiredby"="https://github.com/actions/bin/blob/master/sh/Dockerfile @ 2019 04 24"
LABEL "maintainer"="Lothar Schulz <http://bit.ly/2zVLbWh>"
LABEL "version"="0.0.2"

LABEL "com.github.actions.name"="golang / make / bash for GitHub Actions"
LABEL "com.github.actions.description"="Runs one or more golang / make / bash commands in an Action"
LABEL "com.github.actions.icon"="terminal"
LABEL "com.github.actions.color"="blue"

COPY entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

I followed GitHubs recommendation to create a shell script called entrypoint.sh that is called from the ENTRYPOINT instruction. Please make sure the entrypoint.sh is executable e.g. with chmod +x entrypoint.sh.

A shell script like entrypoint.sh may look like this:

#!/bin/bash

# inspired by https://github.com/actions/bin/blob/master/sh/entrypoint.sh @ 2019 04 24

# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t'

for cmd in "$@"; do
    echo "Running '$cmd'..."
    if sh -c "$cmd"; then
        # no op
        echo "Successfully ran '$cmd'"
    else
        exit_code=$?
        echo "Failure running '$cmd', exited with $exit_code"
        exit $exit_code
    fi
done

Continuous Integration and Continuous Delivery with Github Actions

I experienced recurring patterns of Continuous Integration and Continuous Delivery in the past. From those I wanted to model the following with GitHub actions:

Continuous Integration

  • build source code
  • run various kinds of tests
  • build deployment artifact

Continuous Delivery

  • deploy either on
    • all code changes
    • on specific ones e.g. on pushed to default branch – often master

Workflow

Based on the considerations above I created this workflow:

This workflow gets triggered on pull request events so that every push to a branch belonging to a pull request triggers a workflow run.

Continuous Integration stage

There are three parallel actions triggered in the first Continuous Integration stage:

Unit and benchmark action are the only golang specific actions.
Post a gif on fail is calling the shaking-finger-action. This Github Action displays a gif of Conan O’Brien shaking his finger to a pull request on fail.
Even if the subsequent action is failing, shaking-finger still posts a gif to the pull request.

Continuous Delivery stage

In this showcase setup, I consider passing tests good enough to build a Docker image in the next action.
In case the Docker image is created successfully, a subsequent action performs a log-in to DockerHub. Pushing the created image is the last step in this workflow. This completes the delivery in this showcase setup.
I use $GITHUB_SHA environment variable to tag the docker image. GitHub sets some environment variables by default.

Docker image tags on docker hub
Docker image tags on docker hub

Github Actions offer an option to store secrets. The easiest way is to add secrets is using the visual workflow editor. I used secrets to talk to the GitHub API as well as to push to DockerHub.

Wishes & Learnings

I really enjoyed playing around with GitHub Actions and I would consider GitHub Actions in production once it is general available. I would love to experience the following details about actions and workflows differently in the future.

Workflow level environment-variables

Unfortunately, environment-variables can only be set currently on action level. I’d love to define environment-variables on workflow level as well. Environment-variables defined on workflow level shall be effective for all actions referenced by the workflow. In case the same environment-variable is defined on workflow and action level (and the action is part of the workflow), the environment-variable on action level would take preference.

My use case regarding workflow level environment-variables is:

  1. there is one action to build a docker image
  2. there is a subsequent action to login to docker hub
  3. there is a subsequent action to push to docker hub

The docker image tag in step 1 and 3 is currently repeated because I can’t define a workflow level environment-variable like

DOCKER_IMAGE=”lotharschulz/hello-github-actions:$GITHUB_SHA”

Store environment

Store-env is a Github Action listed in the Marketplace that stores its environment in ~/.profile file. Unfortunately this is not a workaround for the use case described above.
Whenever I used store-env to store environment variables the subsequent step build “docker.build” failed with

/entrypoint.sh: export: line 7: test,: bad variable name e.g.

Whenever the step using store-env wasn’t present the exact same step “docker.build” was successful.

Parallel steps Cancellation

I tested a workflow that includes three parallel actions.

Parallel steps cancellation
Parallel steps cancellation

Actions benchmark, test, post gif on fail are defined to run in parallel; there is a subsequent step that needs actions test. If one of the parallel steps fails, I’d expected the remaining parallels step to finish. However, these steps get cancelled as in the screenshot above.

Exit code “neutral” 78

Github Actions can be configured to return the exit code 78 “neutral” in case an action fails. This exit code “indicates that the action terminated but did not fail”.

NEUTRALCODE=78
for cmd in "$@"; do
    echo "Running '$cmd'..."
    if sh -c "$cmd"; then
        # no op
        echo "Successfully ran '$cmd'"
    else
        exit_code=$?
        echo "Failure running '$cmd', exited with $exit_code. Action will return $NEUTRALCODE 'neutral'."
        exit $NEUTRALCODE
    fi
done

https://github.com/lotharschulz/hello-github-actions/blob/neutralFailure20190502/action-neutral/entrypoint.sh#L8-L18

In the screenshot below you see the GitHub Action benchmark “fail” with exit code 78 “neutral”.

That causes the cancellation of the other actions: test & post git on fail.

Cancellation of the other actions
Cancellation of the other actions

The respective log contains:


Exit code 78 does not offer a workaround for the Canceling parallel steps situation.

Start up delays

I also noticed some times a couple of seconds delay between git push to origin and the first action in the workflow being started. This is just a feeling because I did not measure the delay.

Conclusion

GitHub actions let you automate almost everything on GitHub. I dove into a CI/CD use case to showcase that. The ability to run actions defined on your own or use open source actions that exist already offers a lot of options. Being able to define native global workflow level environment-variables would be an important step to use GitHub Actions in production environment once it is generally available.

Further reading


This post is inspired by Bas Peters’ talk GitHub Actions: Open Source Workflow Automation by Bas Peters in Vilnius 2019.

Thanks for reviewing this article: Bas Peters and Johannes Nicolai


Lothar Schulz

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.