Automating the release process of Terraform modules with semantic versioning

Alain Reguera Delgado
Globant

--

Releasing new changes for your Terraform modules implies maintaining version numbers that follow semantic versioning specification. When your project is small, such task doesn’t look like a big deal, considering that all you have to do is pushing a tag in the form x.y.z to your git repository. However, at some point, you ask yourself if there is any way for this process to happen automatically, so you can spend more time on your code and less wondering the correct order of numbers in versions and pushing tags.

There are several options out there to help you automate semantic versioning in your projects. The vast majority of them take the conventional commit specification as reference to analyze the format of commit messages in specific branches of your repository and, based on this analysis, determine the correct numbers of the version.

This article explores the combination of semantic-release, commitizen, and GitHub Actions to automate the release process in the terraform-aws-ssm project, a Terraform module used to compose AWS Systems Manager (SSM) configurations. Sections are organized step by step, so you can reproduce them on your own projects. The main sections are:

  1. Step 1. Install NPM package manager.
  2. Step 2. Install and configure NPM packages
  3. Step 3. Understand conventional commits
  4. Step 4. Check the release process automation
  5. Conclusion

Step 1. Install NPM package manager

The semantic-release and the conventional tools used in this article are written in JavaScript, and distributed as a package in the npmjs.org registry. To install these packages, the NPM package manager must be present in the operating system you are using as workstation or container image on GitHub actions.

If you are using Fedora Linux, or similar, do the following:

  1. Install NPM installing nodejs package.
$ sudo dnf install nodejs

2. Create the ~/.npm/packages directory to store global NPM packages (i.e., those installed using the -g option).

$ mkdir ~/.npm/packages/

3. Update your ~/.bashrc file, to configure environment variables.

NPM_PACKAGES="${HOME}/.npm/packages"
export PATH="${PATH}:${NPM_PACKAGES}/bin"
export MANPATH="${MANPATH-$(manpath)}:${NPM_PACKAGES}/share/man"

4. Check NPM was installed successfully:

$ npm --version
8.3.1

Step 2. Install and configure NPM packages

The package.json file

The package.json file describes the NPM packages you’ll install in your project. Normally, this file doesn’t exist and you need to create it. To install and configure semantic release in the repository, do the following:

  1. Create the package.json file running the npm init command:
$ npm init

When you execute npm init command, it asks you a number questions that you can respond or skip. Not all the attributes created by default in the package.json file are required for the purpose of automating releases. The most relevant attributes here are devDependency to track software versions, and config to configure software usage.

2. Run the following commands to install semantic-release and its plugin for GitHub:

$ npm install -g commitizen
$ npm install --save-dev git-cz
$ npm install --save-dev semantic-release @semantic-release/github

3. Edit the package.json file to configure commitizen with git-cz.

{
"config": {
"commitizen": {
"path": "git-cz"
}
}
}

4. Edit the package.json file to configure plugins.

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

Note how the npmPublish has been set to false in @semantic-release/npm plugin to prevent semantic-release from trying to push anything to nodejs.org registry. This accelerates a bit the release process in projects like terraform-aws-ssm that don’t need to publish packages in npmjs.org registry. There are more configuration options you can change to meet your needs. To know more about these plugins, see the following links:

5. Edit the package.json file to configure the branch names that semantic-release program will analyze commit messages on to create a version number based on them.

{
"release": {
"branches": ["main"]
}
}

After installing semantic-release, conventional commit tools, remove some unused attributes, and edit to add others, the package.json used in terraform-aws-ssm looks like the following:

The package.json file of terraform-aws-ssm project

The package-lock.json file

The package-lock.json is produced from the package.json and it must be under version control, so you can use npm ci command later on during CI/CD configuration.

The .github/workflow/release.yml file

The .github/workflow/release.yml file specifies the workflow GitHub actions will execute. This is the configuration that controls the automation itself, where semantic-release is executed and the release version created.

---
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 'lts/*'
- name: Install dependencies
run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release

This configuration can be improved in many ways (e.g., adding tests before executing the release job). It is a starting point to create a release when someone push changes into the main branch using conventional commit specification and motivate you to keep digging in this topic. To know more on how to configure semantic-release with GitHub Actions, see Using semantic-release with GitHub Actions.

The node_modules directory

The node_modules directory stores the software packages specified in the package.json file when you run commands likenpm install or npm update. This directory can reach near the 150MB in size, considering the content of package.json shown above, and must be excluded from versioning control adding a line for it in .gitignore file.

The v1.0.0 tag

The semantic-release implementation expects you to have at least one tag with the name v1.0.0 in your repository. Normally, this tag doesn’t exit and you need to create it manually. That is the starting point for all the semantic version analysis. So, check you have a v1.0.0 tag in your repository and create it if you don’t have it.

Watch Stephan Bönnemann’s video about semantic versioning.

Step 3. Understand conventional commits

At this point your workstation is ready to record conventional commits. To do so, after a git add command, use the cz command instead of git commit command.

The user interface of conventional commits

In your workstation terminal, the cz commands presents an interactive interface that guides you through the process of making the conventional commit. It looks like the following:

Conventional commits user interface in the terminal.

On GitHub web interface, conventional commits look like the following:

Conventional commit format in the GitHub commits interface.

Customizing the conventional commit user interface

In case you want to have different messages, you can customize this interface to fit your needs. However, keep in mind that semantic-release is all about conventional commit messages, and whatever change you perform here must also be changed in the configuration of semantic-release too.

Understanding the relation between commit message and version numbers

The release version is controlled by three different commit messages. From the Conventional Commits 1.0.0 specification:

The commit contains the following structural elements, to communicate intent to the consumers of your library:

1. fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).

2. feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).

3. BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.

Step 4. Time to check the automation

GitHub Actions

Looking on terraform-aws-ssm project GitHub Action, it is possible to observe some failed jobs, and the one that succeed was not the one where the v1.0.1 version was created. What happened here?

Let’s take a closer look at the commits pushed to repository's main branch since the v1.0.0 tag:

All the commits here start with chore: except three of them. Interesting to see that there is a ci: string in the body of one of the commits, created as the result of a squashed merge of three commits in compliance with conventional commit specification. The semantic-release program parsed all the commits between the v1.0.0 tag and the last commit in the repository and, as a result, found one ci: string and triggered a new PATCH version because of it.

Looking into that specific job, it is possible to observe that it was the one responsible for creating the v1.0.1 version, before it failed trying to publish content to npmjs.org registry.

Semantic-release runs in GitHub Actions creating a new version.

Later, when the package.json file was changed again, adding other chore: commit, to avoid publishing content to npmjs.org registry, semantic-release ran again, but it didn’t create any version because chore: commits in the conventional commit specification doesn’t affect semantic versioning. They just pass.

Semantic-release runs on GitHub Actions without creating a new version.

GitHub Tags

Looking on terraform-aws-ssm project GitHub tags, it is possible to observe a new tag created automatically. Awesome!

New version available in GitHub tags

Terraform Registry

Looking on Terraform registry for terraform-aws-ssm project, it is also possible to observe that a new version has been automatically selected when you visit the module’s page. Awesome!

New module version available on Terraform registry

Conclusion

This article explored, step by step, how to automate the release process of Terraform modules using semantic-release, conventional commits, and GitHub Actions. The automation achieved in the article allows developers to dedicate more time coding and less time planning version numbers and pushing the related tags to the repository. The configuration is flexible and can be adapted to other projects.

Thanks!

--

--