Categories
Posts in this category
- Automating Deployments: A New Year and a Plan
- Automating Deployments: Why bother?
- Automating Deployments: Simplistic Deployment with Git and Bash
- Automating Deployments: Building Debian Packages
- Automating Deployments: Debian Packaging for an Example Project
- Automating Deployments: Distributing Debian Packages with Aptly
- Automating Deployments: Installing Packages
- Automating Deployments: 3+ Environments
- Architecture of a Deployment System
- Introducing Go Continuous Delivery
- Technology for automating deployments: the agony of choice
- Automating Deployments: New Website, Community
- Continuous Delivery for Libraries?
- Managing State in a Continuous Delivery Pipeline
- Automating Deployments: Building in the Pipeline
- Automating Deployments: Version Recycling Considered Harmful
- Automating Deployments: Stage 2: Uploading
- Automating Deployments: Installation in the Pipeline
- Automating Deployments: Pipeline Templates in GoCD
- Automatically Deploying Specific Versions
- Story Time: Rollbacks Saved the Day
- Automated Deployments: Unit Testing
- Automating Deployments: Smoke Testing and Rolling Upgrades
- Automating Deployments and Configuration Management
- Ansible: A Primer
- Continuous Delivery and Security
- Continuous Delivery on your Laptop
- Moritz on Continuous Discussions (#c9d9)
- Git Flow vs. Continuous Delivery
Sun, 24 Apr 2016
Automating Deployments: Version Recycling Considered Harmful
Permanent link
In the previous installment we saw a GoCD configuration that automatically built a Debian package from a git repository whenever somebody pushes a new commit to the git repo.
The version of the generated Debian package comes from the debian/changelog
file of the git repository. Which means that whenever somebody pushes code or
doc changes without a new changelog entry, the resulting Debian package has
the same version number as the previous one.
The problem with this version recycling is that most Debian tooling assumes that the tuple of package name, version and architecture uniquely identifies a revision of a package. So stuffing a new version of a package with an old version number into a repository is bound to cause trouble; most repository management software simply refuses to accept that. On the target machine, upgrade the package won't do anything if the version number stays the same.
So, its a good idea to put a bit more thought into the version string of the automatically built Debian package.
Constructing Unique Version Numbers
There are several source that you can tap to generate unique version numbers:
- Randomness (for example in the form of UUIDs)
- The current date and time
- The git repository itself
- GoCD exposes several environment variables that can be of use
The latter is quite promising: GO_PIPELINE_COUNTER
is a monotonic counter
that increases each time GoCD runs the pipeline, so a good source for a
version number. GoCD allows manual re-running of stages, so it's best to
combine it with GO_STAGE_COUNTER
. In terms of shell scripting, using
$GO_PIPELINE_COUNTER.$GO_STAGE_COUNTER
as a version string sounds like a
decent approach.
But, there's more. GoCD allows you to trigger a pipeline with a specific
version of a material, so you can have a new pipeline run to build an old
version of the software. If you do that, using GO_PIPELINE_COUNTER
as the
first part of the version string doesn't reflect the use of an old code base.
To construct a version string that primarily reflects the version of the git repository, and only secondarily the build iteration, the first part of the version string has to come from git. As a distributed version control system, git doesn't supply a single, numeric version counter. But if you limit yourself to a single repository and branch, you can simply count commits.
git describe
is an established way to count commits. By default it prints
the last tag in the repo, and if HEAD
does not resolve to the same commit as
the tag, it adds the number of commits since that tag, and the abbreviated
sha1 hash prefixed by g
, so for example 2016.04-32-g4232204
for the commit
4232204
, which is 32 commits after the tag 2016.04
. The option --long
forces it to always print the number of commits and the hash, even when HEAD
points to a tag.
We don't need the commit hash for the version number, so a shell script to construct a good version number looks like this:
#!/bin/bash
set -e
set -o pipefail
version=$(git describe --long |sed 's/-g[A-Fa-f0-9]*$//')
version="$version.${GO_PIPELINE_COUNTER:-0}.${GO_STAGE_COUNTER:-0}"
Bash's ${VARIABLE:-default}
syntax is a good way to make the script work
outside a GoCD agent environment.
This script requires a tag to be set in the git repository. If there is none,
it fails with this message from git describe
:
fatal: No names found, cannot describe anything.
Other Bits and Pieces Around the Build
Now that we have a version string, we need to instruct the build system to use
this version string. This works by writing a new entry in debian/changelog
with the desired version number. The debchange
tool automates this for us.
A few options are necessary to make it work reliably:
export DEBFULLNAME='Go Debian Build Agent'
export DEBEMAIL='go-noreply@example.com'
debchange --newversion=$version --force-distribution -b \
--distribution="${DISTRIBUTION:-jessie}" 'New Version'
When we want to reference this version number in later stages in the pipeline (yes, there will be more), it's handy to have it available in a file. It is also handy to have it in the output, so two more lines to the script:
echo $version
echo $version > ../version
And of course, trigger the actual build:
debuild -b -us -uc
Plugging It Into GoCD
To make the script accessible to GoCD, and also have it under version control,
I put it into a git repository under the name debian-autobuild
and added the
repo as a material to the pipeline:
<pipeline name="package-info">
<materials>
<git url="https://github.com/moritz/package-info.git" dest="package-info" />
<git url="https://github.com/moritz/deployment-utils.git" dest="deployment-utils" materialName="deployment-utils" />
</materials>
<stage name="build" cleanWorkingDir="true">
<jobs>
<job name="build-deb" timeout="5">
<tasks>
<exec command="../deployment-utils/debian-autobuild" workingdir="#{package}" />
</tasks>
<artifacts>
<artifact src="version" />
<artifact src="package-info*_*" dest="package-info/" />
</artifacts>
</job>
</jobs>
</stage>
</pipeline>
Now GoCD automatically builds Debian packages on each commit to the git repository, and gives each a distinct version string.
The next step is to add it to a repository, so that it can be installed on a
target machine with a simple apt-get
command.
I'm writing a book on automating deployments. If this topic interests you, please sign up for the Automating Deployments newsletter. It will keep you informed about automating and continuous deployments. It also helps me to gauge interest in this project, and your feedback can shape the course it takes.