Automated release notes

Dev Diary

It is a great feeling when you've released your quality code with confidence to production. After that, the release notes have to get sent out to the interested parties. You write down on a list what exactly was in the release. It could be that a lot of changes were made, then the question of "what exactly was in the release?" comes up.

If you didn't track what went out in a release (e.g. related issues from the issue tracking system) it can be quite a chore to go back and check what exactly was put onto the production system. Is it possible to automate this? Yes. In this blog post I will show you how we automated this process.

Author
Arjan Frans
Date
November 2, 2022
Reading time
5 Minutes

Tools we use

Gitlab and Docker are the two of the most important tools that we use to do releases. Our setup completely automates the builds and deployments of our applications. Gitlab has a feature called "Releases". You can either manually create a release document or create it directly from a CI/CD pipeline. Releases created in Gitlab are based on tags. Based on the changes between tags we know what changes have been made. Of course we chose to use the automated way of using releases.

New workflow rules

In order to be able to automate something there has to be structure. A good way to start is checking what is already available: merge requests can be linked to Gitlab issues. This is key to creating a relation between your code and the issues that will eventually end up in the release notes.

An existing specification for writing code commits is Conventional Commits. It also has a Node.js package that can be used to generate a changelog: conventional-changelog-cli. At first this seems like an easy thing to integrate, however there are some things to consider depending on your existing workflow. At Fusonic we use a squash + fast-forward merge workflow. In short this means that a merge request will eventually end up as one commit containing the merge request title in the message. I am skipping the explanation of the Conventional Commits convention, as you can read it on the website. Here are some examples of what we now name our merge requests: 

  • fix(#2): entity not persisted
  • fix(#5): adjust incorrect response
  • refactor(#3): change structure of controllers
  • infra: increase disk space
  • ci(#4): add codestyle linting

A category, an optional issue reference and a short description. Based on this information we can generate a changelog.
 

Enforcing MR title conventions

To make everything consistent it is important to enforce the above described rules. A tool for validating the commit messages already exists, a Node.js package called Commitlint. A configuration can be created to enforce the conventions we use. Commitlint uses the parser from the Conventional Commit set of tools. With an optionally tweaked configuration we can now validate our merge request titles.

Implementation

Building a Docker image

Instead of configuring the release option in a Gitlab CI/CD job we have extended the Gitlab Release CLI Docker image to give ourselves some more flexibility. Installing Node.js and the packages required for the Conventional Commit tools now allows us to lint and generate the eventual changelog with a single Docker image. Our Dockerfile looks like this:

FROM registry.gitlab.com/gitlab-org/release-cli:latest RUN apk update && apk add nodejs yarn git RUN yarn global add conventional-changelog-cli @commitlint/cli COPY ./commitlint.config.js /release-config/commitlint.config.js COPY ./config.js /release-config/config.js RUN cd /release-config && yarn

We publish this Docker image into our private container registry. This image can then be used by different projects.

Configuring the pipeline

In our project's Gitlab configuration we can use our created Docker image to validate the merge request titles and generate the release with release notes. Validating the merge request titles is only done if a branch has a merge request. The configuration looks like this:

release:lint-merge-request: stage: release image: release-tool-image:latest script: - echo ${CI_MERGE_REQUEST_TITLE} | commitlint -g /release-config/commitlint.config.js -p /release-config/config.js rules: - if: '$CI_MERGE_REQUEST_TITLE =~ /^wip:/i || $CI_MERGE_REQUEST_TITLE =~ /^draft:/i' when: never - if: $CI_MERGE_REQUEST_ID when: always

When you create a release, you have to specify a version. You could read it automatically, for example from the package.json file in a Node.js project. In the example below the job has to be started manually and requires you to write the verison in a variable.

release:create: stage: release image: release-tool-image:latest variables: GIT_FETCH_EXTRA_FLAGS: '--tags -f' before_script: - if [ -z "$RELEASE_VERSION" ]; then echo "You must set a RELEASE_VERSION"; exit 1; fi script: - conventional-changelog --outfile description.md --config /release-config/config.js - release-cli create --name "Release $RELEASE_VERSION" --description description.md --tag-name "$RELEASE_VERSION" --released-at "$CI_PIPELINE_CREATED_AT" rules: - when: manual

The result

Once all the desired merge requests have been merged we can create a release on the main branch. Note: you should always release from one branch, otherwise you might have a conflicting history. Which branch is up to you. After starting the release:create pipeline job you can see a new release in your project under Deployments -> Releases. Here is what it will look like.

Selection_665
A clean categorized overview of all the commits and possible related issues that are included in this release. All automatically generated.

For the complete source code check out this Github repository.

More of that?

decorating commands
Dev Diary
Decorating Commands
August 3, 2022 | 2 Min.

Contact form

*Required field
*Required field
*Required field
*Required field
We protect your privacy

We keep your personal data safe and do not share it with third parties. You can find out more about this in our privacy policy.