Gitflow and CI/CD at Pistil
A couple of weeks ago I joined Pistil Data, a cannabis data analytics startup. Coming into the engineering team, which is very much being formed, I noticed how every commit was being done directly to the master
branch, and every deploy to dev
, staging
and prod
, from the master
branch as well. The team being small, this does make sense, but we’re looking into expanding it, and as we have more people working on the same codebase, we’ll need a better structure and flow around branches, pipelines and deployments, hence Gitflow.
Gitflow
You should read Atlassian’s post on gitflow (and the original post), but the basic idea is that a project using git should have the following branches:
master
represents production at all timesrelease
code coming fromdevelop
, about to be merged intomaster
to be released to productionhotfix
branched out offmaster
, to fix production bugs. After it’s done and tested, it should be merged back intomaster
so the fix goes out to production, and intodevelop
so that the next release doesn’t lose itdevelop
contains all ofmaster
’s code, and it’s usually ahead. It represents code/features that are going out in the next release. Ideally,develop
should represent your staging environment at all times, just likemaster
represents productionfeature-[\d+]
feature branches, offdevelop
. The number is a simple reference to your board system (Jira, GH issues, Zenhub, etc). Once the developer finishes working on the feature, they should open a pull request againstdevelop
From the branches laid out above, we decided to drop release
, since it adds even more overhead to the release cycle.
Azure DevOps
At Pistil, we use Azure DevOps to host our repositories, our Agile board, and as you’ll see below, our CI and CD pipelines.
It’s very similar to any of the other tools out there, like Github, Gitlab or Bitbucket.
The Plan
All that said, when we set out to implement gitflow and fix the CI/CD pipelines around it, this was the plan:
- the
master
anddevelop
branches should be locked for commits, only accepting pull requests - any commit to
feature-[\d]+
branches should trigger a CI pipeline that runs unit tests, and blocks existing pull requests againstdevelop
if it fails - any merge into
develop
should:- trigger a CI pipeline to build the project’s artifact, and also runs the unit tests. If this pipeline fails, any pull request against
master
should be blocked - trigger a separate CI pipeline to run the project’s Integration Tests Our Integration Tests take too long to run, and currently we’re accepting the trade-off of having them in a separate pipeline that doesn’t block pull requests.
- trigger a release CD pipeline to automatically deploy the build to the
dev
environment - trigger a separate release CD pipeline to create a release to
staging
. The actual deploy must still happen manually (sort of a business requirement for us at the moment, ideally it should deploy tostaging
automatically as well)
- trigger a CI pipeline to build the project’s artifact, and also runs the unit tests. If this pipeline fails, any pull request against
- any merge into
master
should:- trigger a CI pipeline to build the project’s artifact
- trigger a separate CI pipeline to run the project’s Integration Tests
- trigger a release CD pipeline to deploy to
prod
manually
End result
All in all, the plan has been accomplished, except for the Integration Tests CI pipeline, which were set to run manually.
Below are our default branches develop
and master
. At the time of writing, we’ve merged and deleted every feature-[\d]+
branch into develop
and then into master
for the latest release, that’s why there’s no other branch:
Notice the blue badge, close to the develop
branch, reading “Branch has policies”. It represents a policy that blocks every pull request against develop
if the unit tests have failed. There’s no matching blue badge in the master
branch because I still don’t have the necessary Azure DevOps permission to add one to a repository’s default branch, but I’ll add one soon.
Below, the relevant CI pipelines that run unit and integration tests, and build the project:
- 2 for the
develop
branch: Integration Tests + (Build + Unit Tests) - 2 for the
master
branch: Integration Tests + Build - 1 for any
feature-[\d]+
branch: Unit Tests
The CD pipelines:
- one that releases the
develop
branch automatically to thedev
environment, and manually to thestaging
environment - another that releases
master
to theproduction
environment, manually
And finally, a quick look at how the develop
CD release pipeline looks in Azure DevOps:
Next steps
A few things we have left to improve the process and pipelines:
- Azure DevOps has something called Environments which could, I think, replace our current stages on the release pipelines, but from my readings, I couldn’t clearly understand the benefits of going with Environments, so more reading on that is necessary
- A bit outside of AzureDevOps’ scope, but Azure App Services has a “Deployment” section with “Deployment Slots” and “Deployment Center” in there. It’s something worth exploring