Automating Semantic Versioning with Github Actions and Branch Naming Conventions
Achieving Consistent and Automated Versioning Across Your Projects with Github Actions
In a previous post, we talked about how to deploy preview environments on Kubernetes using Github Actions:
In this article we will focus on how to automate a semantic versioning deployment and release cycle by combining a release branch naming convention and Github Actions.
Introduction
In the fast-paced world of software development, maintaining consistent and meaningful version numbers can be challenging. Semantic Versioning (SemVer) provides a standardized way to communicate changes in your software. This article explores how to automate a release of your repository with Semantic Versioning using GitHub Actions and effective branch naming conventions.
Understanding Semantic Versioning
Semantic Versioning, often abbreviated as SemVer, is a versioning scheme for software that aims to convey meaning about the underlying changes in a release through version numbers. It was created by Tom Preston-Werner and is widely adopted in the software development industry. SemVer consists of three components: major, minor, and patch versions, represented as MAJOR.MINOR.PATCH.
Major Version (MAJOR): This digit is incremented when incompatible changes are introduced in the software. It signifies that there are breaking changes in the codebase, and developers should expect potential backward compatibility issues.
Minor Version (MINOR): When new features or enhancements are added in a backward-compatible manner, the minor version is incremented. Developers can safely update to a new minor version without worrying about breaking changes.
Patch Version (PATCH): The patch version is incremented for backward-compatible bug fixes and minor improvements that do not introduce new features or breaking changes.
More details here: https://semver.org/
The Importance of Semantic Versioning
Semantic Versioning plays a pivotal role in software development by promoting clarity, predictability, and compatibility in software updates. By adhering to SemVer, developers and users of a library or package can anticipate the impact of an update. They can quickly assess whether an upgrade is safe or might require adjustments to their codebase.
Git Flow
This post assume you are using a standard Git Flow with develop / master (main) / feature / release branches:
Feature branches are created from develop and then merged back to develop
[Optional] This merge should be a squash merge (so you can rollback an entire feature without getting all of its commits while cherry picking).
Release branches are created from develop in order to create a snapshot of what’s next to be released and then merged to master (main)
[Required] This particular merge has to be a Merge Commit (so you can retrieve the release branch name as part of the commit).
Github Actions
Pseudo code
name: release-production
on:
workflow_dispatch:
push:
branches:
- "master"
jobs:
git-release:
uses: myworkflows/git-release.yml
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}
build:
uses: myworkflows/docker-build-push.yml
needs: [git-release]
with:
repository: ${{ github.event.repository.name }}
docker-tag: ${{ needs.git-release.outputs.semver }}
deploy:
uses: myworkflows/deploy.yaml
needs: [git-release, build]
with:
target: my-api-staging
docker-tag: ${{ needs.git-release.outputs.semver }}
git-release:
This workflow contains 2 steps, one that will attempt to get the semantic versioning from the merge commit and another one that creates a release / tag in Github.
name: git-release
on:
workflow_call:
secrets:
github-token:
required: true
outputs:
semver:
value: ${{ jobs.release.outputs.semver }}
description: semver from our release branch name
jobs:
release:
name: "Extract semver from release branch"
runs-on: ubuntu-latest
outputs:
semver: ${{ steps.semver.outputs.value }}
steps:
- name: Extract semver from release branch name
shell: bash
id: semver
run: |
export SEMVER=`echo "${{ github.event.head_commit.message }}" | grep -i "release-" | cut -d '-' -f 2 | head -n 1`
echo "value=$SEMVER" >> $GITHUB_OUTPUT
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.github-token }}"
automatic_release_tag: "${{ steps.semver.outputs.value }}"
prerelease: false
draft: false
title: "Release ${{ steps.semver.outputs.value }}"
It assumes you will name your release branch with this format: release-X.Y.Z or if you want to use “v” like me: `release-vX.Y.Z`
You can test in your shell, a merge commit usually looks like this:
“Merge pull request #3 from org/release-X.Y.Z”
echo "Merge pull request #3 from org/release-X.Y.Z" | grep -i "release-" | cut -d '-' -f 2 | head -n 1
Then we use a community Github workflow from mavinpinto called action-automatic-releases.
Conclusion
The output of this git-release workflow can then be sent to the build and deploy workflows, meaning it can build a docker image with the semver as tag and then your deploy workflow can deploy that exact container via its semver tag.
This is not fully automated simply because it’s always good for developers to decide the Major, Minor and Patch increments manually. Releases are not perfect and sometimes merge different features that have different impact all together.
It’s better to have some control over this than rely on a fully automated system in my opinion.