semantically and automatically bump apps versions and releases using GitLab pipeline

Mohammed Ragab
Mar 13 · 6 min read

The problem that we are trying to solve in this article we need control, versioning, and tagging our apps semantically and automatically to reduce human mistakes

Our goal is to solve this problem using GitLab CI/CD and semantic-release

What is semantic-release?

semantic-release automates the whole package release workflow including determining the next version number, generating the release notes, and publishing the package.

How does it work?

The key here is your commit message format, semantic-release uses the commit messages to determine the type of changes in the codebase. Following formalized conventions for commit messages, semantic-release automatically determines the next semantic version number, generates a changelog, and publishes the release.

For example, if you need to add a hot-fix your commit message should be written in this format

fix(pencil): {Your message}

And when you merge a pull request with one of the releases branches you should have a patch release, for example, if the last created tag from the branch master is 1.0.0 after merge and bump release you should have a new tag 1.0.1

If you want to add a feature your commit message should be like

feat(pencil): {Your message}

And after that, your next release from the release branch will be 1.1.0 if the last tag was 1.0.0 and so on you can find all formats here

https://github.com/semantic-release/semantic-release

How can I use it?

To install “Semantic release” you should have node ≥= 10.18

then run the following command to install the package

npm install --save-dev semantic-release

and for other types of applications such as python or maven, you can globally install it in your CI environment, and semantic supports a lot of popular CI environments such CircleCI, Github actions, and GitLab and we will see this later.

After installing the package you should define the semantic release configuration this step can be done in more than one way

I highly recommend using “.releaserc” file in “yaml” or “json” or js format and you can use your package.json file to configure it as well

we need to tell “semantic” about our releases branches

{
"release": {
"branches": ["master", "next"]
}
}

This configuration will make “semantic” skip any other branches from creating a release outside the branches list

you can also configure the plugin you need like

{
"plugins": ["@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/npm"]
}

and you see more about plugins from here

https://github.com/semantic-release/semantic-release/blob/master/docs/usage/plugins.md

After creating the configuration file and after your commit changes with the valid format or merge request to the release branch you can run the following command to generate version and creating the tag

npx semantic-release

what is npx ?

npx — short for “npm exec” — is a CLI to find and execute npm binaries within the local node_modules folder or in the $PATH. If a binary can’t be located npx will download the required package and execute it from its cache location. The tool is bundled with npm >= 5.2, or can be installed via npm install -g npx.

More complex case

If we created, for example, a new feature of our app and version became 1.1.0 and some of our clients or users still use 1.0.0 and they do not use the featured one and they report a bug on 1.0.0 that need to be maintained what can we do in this case.

semantic-release allows to manage and automate complex release workflow, based on multiple Git branches and distribution channels.

  • Distributes certain releases to a particular group of users via distribution channels
  • Manage the availability of releases on distribution channels via branches merge
  • Maintain multiple lines of releases in parallel
  • Work on large future releases outside the normal flow of one version increment per Git push

so in our case is about maintenance in this case we need to configure our branches to support maintenance flow.

branches: [
"+([0-9])?(.{+([0-9]),x}).x",
"master"
]

In the configuration, we are telling semantic that any branch with for example this pattern 1.0.x or 1.1.x and so on is a release branch so we can do the following steps to maintain the old version

  • Create a branch from the old tag for example we need to maintain v1.0.0 without the changes on the latest 1.1.0 that contains the new feature
 git checkout -b 1.0.x v1.0.0
  • Add your hotfix and commit with a message like an example
git commit -m "fix(pencil): {Your message}" 
  • Push your branch
  • Then run semantic release on this branch
npx semantic-release

Now we will have a new hotfix tag 1.0.1

there are a lot of options to do using workflow you can check this like

https://github.com/semantic-release/semantic-release/blob/master/docs/usage/workflow-configuration.md

What semantic does behind the scenes?

If you want to see just add flag debug to the release command

npx semantic-release --debug

and the following diagram explains the steps

Semantic in CI/CD using GitLab

In this part, I will explain and create an automatic build, test, release and deploy workflow.

Consider we have a nodejs app or any type of apps and we need to build, test, release version and deploy it in our Kubernetes cluster. of course, I will skip talking about docker build and deploy to K8s but if you want to see this point you can check my previous article after you finish this one 😆

What do we need?

  • Every commit on non-release branches we need to run and build and create a docker image tagged by the branch name for the developer test
  • Every commit on the releases branches we need to create a release based on the latest tag on this branch and depends on the commit message form as we explained before
  • Every tag we need to build, test, create a docker image, and deploy our app into K8s or any kind of environment that we are using it we will not focus on this.
  • Every commit on master we need to update our latest docker image tag

note: For GitLab, we need to configure a required environment variable required by semantic contains GitLab access token to grant semantic access to your repository

GITLAB_TOKEN or GL_TOKEN

Let us write our .gitlab-ci.yml

  • First defined our stages
stages:
- build
- test
- build-non-release-docker-image
- release
- build-release-docker-image
- build-latest-docker-image
- create-new-k8s-app-release
  • Build stage as our app is a nodejs we will use node image in our runner
build:
stage: build
image: node
script:
- echo "Start building App"
- npm install
- npm build
- echo "Build successed!"
artifacts:
expire_in: 1 hour
paths:
- build
- node_modules/
except:
changes:
- "*.md"
- ".gitignore"
  • Test stage
test:
stage: test
image: node
script:
- echo “Testing App”
- npm run test
- echo “Test passed!”
except:
changes:
- “*.md”
- “.gitignore”
  • Build docker image for non-release branches only will trigger on the branch that does not include in release branches
build-non-release-docker-image:
stage: build-non-release-docker-image
image: ubuntu:18.04
script:
- echo “Write scripts to build the docker image”
only:
- branches
except:
refs:
- master
- /^(([0–9]+)\.)?([0–9]+)\.x/
changes:
- “*.md”
- “.gitignore”
  • Our focus step is the release stage only will trigger on the releases branches
release:
stage: release
image: node
before_script:
- echo “preparing environment”
- npm install @semantic-release/gitlab
script:
- npx semantic-release
only:
refs:
- master
- /^(([0–9]+)\.)?([0–9]+)\.x/
except:
changes:
- “*.md”
- “.gitignore”

In this stage, we prepared our environment and install the semantic GitLab integration plugin and run the release command.

  • Create a docker image and deploy stage from the tags only
build-latest-docker-image:
stage: build-latest-docker-image
image: ubuntu:18.04
script:
— echo “image build successed”
only:
— master
except:
changes:
— “*.md”
— “.gitignore”
create-new-k8s-app-release:
stage: create-new-k8s-app-release
image: ubuntu:18.04
script:
— echo “App version $CI_COMMIT_REF_NAME created successfully”
only:
— tags

This stage only trigger on tags

Note: The semantic will add [skip CI] on the commit message on creating the tag that will skip the pipeline trigger on tag creation and it makes sense to avoid triggering build and deploy for tag that created manually but you can customize and dd some workaround to trigger pipeline using GitLab API instead of manual trigger on the tag case.

In the end, I hope I explained it clearly and help you to control and organize your app releases and versions.

Nerd For Tech

From Confusion to Clarification

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Mohammed Ragab

Written by

Software engineer

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.