GitHub Actions in Action
After trying to use GitHub Actions for the first time in my Campsite Booking API project, I migrated three more projects to GitHub Actions. In this article, I want to share my experience in automating software development workflows in GitHub repositories.
Back then, when GitHub couldn’t offer full support for CI/CD pipelines, I opted to use the integration services provided by Travis CI and CircleCI. Therefore, it so happened that the CI workflows for the Vaadin Demo Bakery App and Vaadin Business App projects were implemented using Circle CI, and the CI/CD workflow of the Docker Liferay Portal CE project was implemented with Travis CI. After my first successful experience with GitHub Actions in the Campsite Booking API repository, I decided to migrate the development workflows of my active projects to GitHub Actions.
Campsite Booking API
Earlier, I’ve already described how I replaced Travis CI with GitHub Actions for this project. You can read about that in this post.
Vaadin Demo Apps
I have two GitHub repositories that contain the source code of Vaadin demo applications, namely Bakery App and Business App. The source code in these repositories is generated using Vaadin’s App Starter. In the past, I used to update the code twice a year when I had time, but this year I decided to make updates and releases on a quarterly basis. The source code for the Bakery and Business applications can be found here and here, respectively.
CircleCI - Build
The original continuous integration workflow for both projects was implemented using CircleCI. It was an automatic workflow that was executed whenever a commit was pushed to the master branch. Below is the config.yml
for the vaadin-demo-bakery-app
repository.
version: 2 # use CircleCI 2.0
jobs:
build:
working_directory: ~/vaadin-demo-bakery-app
docker:
- image: circleci/openjdk:8u171-jdk
steps:
- checkout
- restore_cache:
key: vaadin-demo-bakery-app-{{ checksum "pom.xml" }}
- run: mvn dependency:go-offline
- save_cache:
paths:
- ~/.m2
key: vaadin-demo-bakery-app-{{ checksum "pom.xml" }}
- run: mvn -B com.github.eirslett:frontend-maven-plugin:1.7.6:install-node-and-npm -DnodeVersion="v10.16.0"
- run: mvn -B clean package -Pproduction
GitHub Actions - Build
This new automatic workflow replaced the original one, and it differs in that this new workflow is launched not only for every commit to the master branch but also whenever a new pull request to the master is open. Also, instead of using the frontend-maven-plugin plugin to install Node.js and npm, I opted for using the setup-node action.
name: Build Project
on:
pull_request:
branches:
- 'master'
push:
branches:
- 'master'
jobs:
build:
name: Build Project
runs-on: ubuntu-latest
steps:
- name: Check out project
uses: actions/checkout@v2
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Set up Node.js 14
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Run Maven Package
run: mvn -B clean package -Pproduction
GitHub Actions - Release
This manual workflow allows creating a release for every quarterly source code update. It consists of two jobs: Tag Release
and Docker Image
. The Tag Release
job, based on the github-tagger action, will mark the release point by tagging the last commit in the master branch. Unlike the Campsite Booking API project, where I used the Maven Release plugin to make a release, here I took a simplified release approach, namely the simple tagging of the master branch.
As for the release versioning, I chose a numbering system based on Calendar Versioning. In addition, the version number is supplemented by the Vaadin version. So, for example, version number 2021.1-14.5
means that the release was made in the spring of 2021, and the Vaadin version used to generate the source code was 14.5
.
The successful completion of the Tag Release
job is a prerequisite for the Docker Image
job’s subsequent execution when a Docker image is built and published on Docker Hub. The corresponding Dockerfiles can be found here and here.
name: Perform Release
on:
workflow_dispatch:
inputs:
releaseVersion:
description: Release Version
required: true
env:
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
IMAGE_TAG: ${{ github.event.inputs.releaseVersion }}
jobs:
tag_release:
name: Tag Release
runs-on: ubuntu-latest
steps:
- name: Check out project
uses: actions/checkout@v2
- name: Configure Git user
run: |
git config user.email "actions@github.com"
git config user.name "GitHub Actions"
- name: Git tag release
uses: tvdias/github-tagger@v0.0.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.event.inputs.releaseVersion }}
docker_image:
name: Docker Image
runs-on: ubuntu-latest
needs: [ tag_release ]
steps:
- name: Check out project
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.releaseVersion }}
- name: Build image
run: |
docker build . --file Dockerfile --tag $IMAGE_NAME:$IMAGE_TAG
docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
- name: Log into registry
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
- name: Push image
run: |
docker push $IMAGE_NAME:$IMAGE_TAG
docker push $IMAGE_NAME:latest
The Release Version
parameter value should be provided before executing the release workflow:
Docker Liferay Portal CE
The original continuous delivery workflow was implemented using Travis CI and was executed in the following order:
- In the local dev, run the
release-image.sh
script for the new version/variant, which will subsequently commit and push the changes to the remote, that is, to GitHub. - The new commit to the master will trigger a new job on Travis CI.
- The job on Travis CI will do the following:
– build a new image
– run tests
– tag and push the new image to Docker Hub
– update README and supported-tags files
– commit and push back changes to GitHub - In the local dev, pull the changes from the remote and repeat step 1 for the next variant.
As you can see, this workflow was not fully automated as I had to go through some manual steps in my local dev. Therefore, when switching to GitHub Actions, the goal was to fully automate the workflow.
The source code for the Docker Liferay Portal CE project can be found here.
Travis CI - Release
Below is the original .travis.yml
file. Every time the release-image.sh
script was executed in the local dev, the build matrix’s VERSION
and VARIANT
properties were updated with the supplied values.
language: bash
services: docker
branches:
only:
master
env:
global:
- secure: mAQiO76xNoUi0DQ3NKSx/DonUuWWwq0G3ulCydIeGoyWS74yxQ0IMy+4AwSmP/u08XZlXnyTNiCg1+qpw2C53ZstuCEug/NP8EWnIqMU7UDPOdHNYSbm0T7OuoYiocZFw54ELvHg+qhOdPrYI3aq/04o0F1sGMQH8n+AwHzZcxFH6UZv1XtqmG7nDc+F6+26xdx2OH1p9JajqQK9gtoehH7cpxs8fibT3GKUgwSKM2bvCxvXT1vIYtl1S3TGQu9Pd2mVSpt32HVpgfj5PQyRdfCgBHVCVYszxNW+NrhrjzXgrGr5OPSSAwkcI8R6NT4GtW0N1FDhRLbb1J1eq0W6MhpIHIIKC2NdMo870AgnhUjZ3QqJVG3yfqWpbPOrNi0WqyusRygPWI9lAY6BdmpJwUmNaIP9DdX6LR6Zua+wEuFCVFWr11N/qOc58Do6MfXFAHfbNU0p5LGHAwrjdpJN7+XHyTjgVBbQwYdx3GaRzx0P3VdSfgJVhoCNaKSOCbC/fhgJJfdbKYycLxOIZ9HH39lDs3sw9Yh5MsPgK3oMDsiuG2GmUUz5dpmr3C70+nkeA+YHv6+ScoP2JNIaMqx3cDWABI3B5UvPb+wFAasOmKenIvd082OeUKcFkoAGBRExyOc/J1S5Gc+sDyIlcHy5HSvTDLpvgyNpAbMdGvbV+n8=
matrix:
- VERSION=7.3.2-ga3 VARIANT=jdk11-buster
install:
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- git clone https://github.com/docker-library/official-images.git ~/official-images
before_script:
- image="$DOCKER_USERNAME/liferay-portal-ce:$VERSION-$VARIANT"
- latest_image="$DOCKER_USERNAME/liferay-portal-ce:latest"
script:
- |
(
set -Eeuo pipefail
set -x
docker build -t "$image" ./"$VERSION/$VARIANT"
~/official-images/test/run.sh "$image"
docker push "$image"
docker tag "$image" "$latest_image"
docker push "$latest_image"
./script/generate-readme.sh -t "$VERSION/$VARIANT" -c "$TRAVIS_COMMIT"
git status
)
after_success:
- docker images
- ./script/push-remote.sh
GitHub Actions - Release
This new release.yml
file is nothing more than a rewrite of the .travis.yml
, although there are some minor changes. First, the version and variant values no longer need to be hard-coded as they are provided via a workflow dispatch input. Second, the release-image.sh
script has been renamed to release-dockerfile.sh
. And third, the push-remote.sh
script was included in the generate-readme.sh
script.
This is a manual workflow, and The Release Version
parameter value should be provided before executing it. The release version value should follow the LIFERAY_VERSION/JDK-LINUX_VARIANT
pattern, for example, 7.4.0-ga1/jdk8-alpine
. The complete release manual can be found here.
name: Perform Release
on:
workflow_dispatch:
inputs:
releaseVersion:
description: Release Version
required: true
env:
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
IMAGE_TAG: ${{ github.event.inputs.releaseVersion }}
jobs:
release:
name: Dockerfile & Docker Image & README
runs-on: ubuntu-latest
steps:
- name: Check out project
uses: actions/checkout@v2
- name: Configure Git user
run: |
git config user.email "actions@github.com"
git config user.name "GitHub Actions"
- name: Release Dockerfile
run: |
./script/release-dockerfile.sh -t "$IMAGE_TAG"
- name: Check out docker-library/official-images
run: |
git clone https://github.com/docker-library/official-images.git ../official-images
- name: Build image
run: |
docker build -t $IMAGE_NAME:"${IMAGE_TAG//\//-}" ./"$IMAGE_TAG"
../official-images/test/run.sh $IMAGE_NAME:"${IMAGE_TAG//\//-}"
docker tag $IMAGE_NAME:"${IMAGE_TAG//\//-}" $IMAGE_NAME:latest
- name: Log into registry
run: |
echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
- name: Push image
run: |
docker push $IMAGE_NAME:"${IMAGE_TAG//\//-}"
docker push $IMAGE_NAME:latest
- name: Generate README
run: |
./script/generate-readme.sh -t "$IMAGE_TAG" -c "$(git rev-parse HEAD)"
Migrating the development workflows of my active projects to GitHub Actions was a great experience, and I learned a lot from the practical side. Now GitHub Actions will be a great addition to my CICD pipeline building skills, which already include Jenkins, Travis CI, Azure DevOps, and Bitbucket Pipelines.