
How much time I use
GitFlow and
Semantic Versioning , everything does not leave me feeling that something is missing in them. Both concepts are good, but since they offer solutions for problems from different areas, their sharing looks more complicated than they should be.
Perhaps the reason is that I chose not the most optimal way, and this could be a good topic for a future post. In this same I want to describe a simple approach to managing releases of applications and libraries.
Problem
How to make sure that the release build does not get into production without proper integration testing? How to make sure that the NuGet package will not be published until it passes the test with a static analyzer?
')
Most of the tools that could solve these problems already exist, and we often use them “in manual mode”. In fact, you only need to automate all this slightly, but this requires entering appropriate agreements and patterns.
Instruments
We describe the current state of affairs.
Environments
As long as we do not have the capacity or infrastructure to test production, the classic approach would be to use
different environments for different phases of the release life, such as integration or acceptance tests. It would be good to always be sure that a specific assembly of the application will be able to get only to the environment corresponding to it. To do this, we need the ability to uniquely identify each version.
Semantic versioning
Before we go into the details of what exactly we should assign versions, we need to think about how to generally version the software. This topic is not new, and fortunately there is a Semantic Versioning, although in versions of which you
still have to figure it out .
A nice bonus for .NET developers is the
almost complete support of this approach by NuGet and
most public packages .
Gitflow
For most of the projects I've worked with, GitFlow seems like a fairly natural choice to me. If it turns out to be too complicated due to the frequency or number of changes, then you can always “roll back” to
GitHub Flow , where each change is the so-called “hot fix”.
I want to note that for this model Git is not required at all. What actually
write ALM Rangers. Although, with Git, this model works easier than with Subversion.
I will not consider in detail the various strategies for working with branches that are already well
documented .
What is worth forgetting
In the good old days, when most of the tools mentioned above were missing or not so widely used, it was common practice to gradually deploy one version through all environments. One such approach was to commit binary files to a special repository, and to further advance through branches corresponding to different environments. Sometimes this led to meaningless merdzham and even conflicts in the configuration files. Often it was “flavored” with custom deployment scripts for Robocopy.
A large number of manual operations are error prone. As a rule, if there is a possibility to make a mistake, then most likely it will be made. Therefore, please forget this approach, like everything you read in the previous paragraph.
Next approach
Let's take a look at how we can apply the above concepts together with the appropriate tools.
Artifacts
For convenient identification, we version the artifacts produced by the build server: builds and libraries. The basic requirement is the ability to associate an artifact with the source of the change (the commit hash in Git) and the process that generated the artifact (build ID). Most
CI tools have internal assembly counters, which makes this information available.
To work with reusable components (libraries), the standard in the .NET world is the package manager (NuGet). Using the deployment manager, such as
Octopus Deploy , allows you to apply the same concept to all the artifacts supplied. But how exactly to collect and link this meta-information and artifact?
Branches and releases
Instead of using branches corresponding to environments, let's create artifacts that already contain information about the environment they should be assigned to. In other words, we will associate a specific artifact with a specific environment. Knowing what each of the environments is used for, and having this meta-information, we can automatically make decisions about the next stage of deployment.
First we need to determine how stable the artifact is, built from a specific branch:
Branch | Release type | Version format |
---|
feature | alpha | # major. # minor. # revision-a # build # feature |
develop | beta | # major. # minor. # revision-b # build |
release | release candidate | # major. # minor. # revision-r # build |
hotfix | release candidate | # major. # minor. # revision-r # build |
master | stable | # major. # minor. # revision |
Using the above format, we can describe
assembly metadata , for example in general for all assemblies (assebmly) file in a project.
[assembly: AssemblyVersion("#major.#minor")] [assembly: AssemblyFileVersion("#major.#minor.#revision.#build")] [assembly: AssemblyInformationalVersion("#major.#minor.#revision[-prerelease]")]
For
AssmeblyInformationalVersion
we use the format defined in the previous table.
Thus, the versions are numbered according to Semanic Versioning for runtime as well as for identifying the CI build and for the version of the result package.
Using the example of GitFlow-based steps, the versions of the NuGet packages produced by the CI system after a commit to the appropriate branch will look like this:
Task | Branch | CI build number | NuGet Package Version |
---|
Implement feature # 1 | feature-1 | one | 1.2.0-a1feature1 |
Implement feature # 2 | feature-2 | 2 | 1.2.0-a2feature2 |
Implement feature # 1 | feature-1 | 3 | 1.2.0-a3feature1 |
Complete feature # 1 | develop | four | 1.2.0-b4 |
Complete feature # 2 | develop | five | 1.2.0-b5 |
Stabilize release | release-1.2.0 | 6 | 1.2.0-r6 |
Release to production | master | 7 | 1.2.0 |
Fix production issue | hotfix-1.2.1 | eight | 1.2.1-r8 |
Release to production | master | 9 | 1.2.1 |
This pattern well reflects the natural order of increasing release readiness when sorting packages by version.
When creating a release or hotfix, I prefer to increase the version number manually in the general file with metadata and commit it to the repository. Thus, the version number will be relevant for local copies of the project.
Deployments
The important point is to protect against accidental deployment of the wrong environment. As a first line of defense can be a CI server, a crashing build that fails unit tests. In addition, when using GitHub, you can pay attention to the so-called
protected branches .
In Octopus Deploy, there is the concept of
lifecycles , which allows us to gradually release through various environments. However, with the approach considered in this article, we get different artifacts for each of the deployment stages. This means that we need to monitor that each artifact is deposited only on the environment allowed to it.
Release type | Allowed environments |
---|
alpha | D |
beta | T, d |
release candidate | A, T, D |
stable | P, A, T, D |
If our Deployment manager does not support this setup of different life cycles depending on the version of the build
at the native level, then as a rule you can implement it yourself in the form of a simple script. By coding information about the build type into the build version, we get a simple and reliable tool for “safe” and automated creation of releases and their deployment. Such automation is possible, as with the use of Automatic Release Creation in Octopus Deploy, as well as with the so-called Release Train, which is launched according to a schedule.
Alternatives
The same numbers in the release and in the version of the package looks suspicious. In fact, there is no separation of the concepts of release and artifact. This is especially noticeable in the case of NuGet packages for libraries, where it would be nice to have a separate version numbering scheme. Such a scheme can be based on tags in
VCS (do you mark releases with tags?).
Relatively recently, I came across
GitVersion , which supports these concepts natively, and at the same time provides the possibility of flexible configuration. Obviously, it makes sense to pay attention to him.
Conclusion
In this article, we looked at a simple approach to managing release builds of applications and individual components (libraries). I hope these ideas will be useful to you in one form or another.
[Translator's note] This is the ambiguous approach to managing releases. Colleagues, I suggest you tell us how the releases and versioning are arranged for you?Links