Automated Versioning With CI/CD

Bump version: 2020.12.28.2-dev → 2020.12.28.2

Stephan Schrijver
Level Up Coding

--

Photo by Fotis Fotopoulos on Unsplash

In this article, I will show you an example of how to automate versioning — based on date — with CI/CD. I will use Bitbucket and Bitbucket Pipelines, but you can use any Git and CI/CD tool if they expose API endpoints to create, approve, and merge pull requests. I will use bump2version to increase the version number, but the principles can of course be applied with other tooling as well.

Versioning Structure

In our case, we want to structure our versions like 2020.12.28.1 or 2020.12.28.5-dev, where the first three segments combined are the date:year.month.day and the last number is the number of releases that day, optionally with a dev (development) tag, so you can have more releases in one day. You can also choose to replace this release number with the time, so your tag will look like the following: year.month.day.hour.minute or 2020.12.28.09.00.

You will need:

  • Pipelines enabled in your repository.
  • A Bitbucket account for your bot, which has write access to your repository.

Repository Variables

You do not want to add the Bitbucket credentials of the bot account hardcoded in your pipeline configuration. We will set them as (secured) repository variables that we can access within the pipeline. In your repository settings, go to repository variables and add two new repository variables:

  • Name: BITBUCKET_BOT_USERNAME
    Value: the username of the Bitbucket Bot account you have just created.
  • Name: BITBUCKET_BOT_PASSWORD
    Value: the password of the Bitbucket bot account you have just created.
    Also check the Secured checkbox.

Adding Bumpversion Configuration

Bump2version requires a configuration file (.bumpversion.cfg or setup.cfg), where you configure what your version looks like and what files you want to update on a version bump. Please add .bumpversion.cfg or setup.cfg (I used the latter) to your repository its root directory with the following content:

The [bumpversion:file:filename] blocks indicate what files need to be updated on a version bump. It will look for the current tag within the files, in this case 2020.12.28.1, and replaces it with the new tag, for example 2020.12.28.2. Good to know: if bumpversion cannot find the current tag in this file, it will throw an error and your pipeline will fail. So if you do not have a pyproject.toml in your directory, please delete the block or replace it with another file that includes the current version number. Please keep the version.txt block in this file, we will use that file to actually create the correct releases.

Adding a Version File

Add a version.txt file to the root directory of your repository. As content, add the value corresponding to setup.cfg →current_version, in our case 2020.12.28.1. Please make sure version.txt only contains a version number and no additional information!

Adding the Bitbucket Pipeline Configuration File

Add a bitbucket-pipelines.yml file to the root of your project folder and add the following content. Note that this is the minimum setup of the pipeline, which does not contain any deployment logic. I will explain what happens in the pipelines below.

Update March 2022: we’ve encountered issues with tags not being included in the build context anymore. You might want to manually fetch all tags before setting NUMBER_RELEASESusing:

git fetch -all -tags

‘Promote to dev’ Step

I will begin with explaining what happens in the step ‘Promote to dev’. Note that I replaced the other configuration with 3 dots (…) to highlight this particular step, do not use the snippets below but always use the snippet above.

This is a manual step (so you have to push ‘Run’ each time), which can be run when pulling changes into development. In my situation, I wanted to decide when to create a new version. Automating this steps also causes a pipeline loop, which has to be solved with extra logic.

First, the required dependencies are installed. jq is required to parse the JSON responses from the curl commands. bump2version is required to actually create a new version.

After installing the dependencies, the UTC date is fetched and parsed as year.month.day . This will be used later on in our version. But we also need the date to fetch the amount of releases we already created that day.

We can get all git tags starting with the current date and ending with dev (since we want to deploy a create development version) by executing the following command: git tag -l "v${DATE}*-dev". This will output something like:

v2020.12.28.1-dev
v2020.12.28.2-dev

We can then count the number of lines the git tag command outputted with wc -l and store it as a variable called NUMBER_RELEASES .

NUMBER_RELEASES=$(git tag -l "v${DATE}*-dev" | wc -l)

We can use this number later on to calculate our new release number that day.
Next step is ‘dry running’ the bumpversion command (note the --no-commit --no-tag --allow-dirty flags).

The flags make sure the command will not affect the remote state of our repository but temporary bump the new version locally within the pipeline. Then we know what our new version will be, by reading version.txt, and we can create a new release branch including the new version name.

This release branch is used to actually create the new version and for creating a git tag, because I do not want to do this directly on the development branch and I always have write protection on my development branch.

After checking out this release branch within the pipeline, I reset the changes made by bumpversion with git reset — hard HEAD~0, so we can create the new version for real this time.

After bumping this version we push the changes — the commit updating the versions in the file specified in your setup.cfg and the new tag — to the remote repository. We then have a new release branch with a commit that is tagged with our new version.

What you could do next is manually create a pull request from our new release branch to development, but I wanted to automate this step too.

Automatically Merge New Versions
Like I stated before, I also want to automatically merge the release branch into development again. The code below is responsible for creating a pull request and merging it. Again note: I replaced the other configuration with 3 dots.

You can use the Bitbucket API to create a pull request with the endpoint https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_FULL_NAME}/pullrequests. This is a POST request and you have to add the credentials of the Bitbucket user to the request, along with a request body (JSON, escaped as one-liner), which you can see below (prettified).

When the request was successful, you will retrieve a response body including the URL to merge the newly created pull request. You can use jq to parse the response and retrieve the .links.merge.href value. You can then use this value (which is a URL) to make a new curl request. This again is a POST request.

‘Promote to prod’ Step

The ‘Promote to prod’ step is slightly different from the ‘Promote to dev’ step.

This step will only be available when a version with -dev is created and pushed. This will automatically trigger the tag pipeline and you then can manually trigger the ‘Promote to prod’ step. The other thing that is slightly different, is that there will be 2 merges.

Let us assume that we have just created version 2020.12.28.1-dev. When this version is successfully created and pushed to remote, pipelines.tags.v*-dev will be triggered.

When executing the manual step ‘Promote to prod’, the version will be bumped to 2020.12.18.1 (without -dev at the end). The release branch created during this process will be merged into development, but after that development will also be automatically merged into master.

This is far from an ideal situation and could definitely be optimized, because it can happen that new features have been pulled into development in the meanwhile, but for us this will do for now.

You might want to pull the release branch into development and master separately, instead of pulling development into master after pulling the release branch into development, but that is up to you!

Automate Deployments

Not included in the examples were the automated deployments. You can however add build & deploy steps in pipelines.tags.v* or pipelines.tags.v*-dev, depending on your needs.

Questions, Suggestions or Feedback

If you have any questions, suggestions or feedback regarding this article, please let me know!

--

--