Suppose we want to start documenting the changelog, so we can easily track what new features, improvements, bugfixes are delivered in each version. To do that, we need to list the tickets done on every release version.
For this article, we want the changelog note to contain: service name, version, and list of tickets done.
Amazing Service 1.2.0
Features
Techdebt
Bugfix
Other
Imagine the manual work needed to do this:
- list the branches/tickets being merged to current release
- write the changelog
- post to Ms Teams channel
It must be done for every release version. Even doing it for a single service is already a chore. More so if you need to do it for many services, it’ll be hell. Our time will just be spent for administrative task instead of pushing our iterations quicker.
If you’re facing the same problem and already using Jenkins and Ms Teams, then this post may be for you.
Prerequisites
- Using Jenkins with Office365 Connector and this pipeline library:
https://github.com/griddynamics/mpl#use-standard-pipeline-but-with-custom-module - Using Ms Teams channel to post the releases
- Already use git tag to annotate each release
- Using maven release plugin and maven deploy
How to
Suppose we have this kind of merge commits between version 1.1.0 and 1.2.0:
- Merge feature/PROJECT-1232 to release/1.2
- Merge techdebt/PROJECT-1241-improve-query-performance to release/1.2
- Merge feature/PROJECT-1231 to release/1.2
- Merge bugfix/PROJECT-1252-fix-intermittent-error to release/1.2
- Merge bugfix/PROJECT-1251 to release/1.2
- Merge PROJECT-1301 to release/1.2
In general, we need to:
- Create a project-specific configuration in Jenkinsfile
- Create a step file: {ProjectRepo}/.jenkins/modules/{Stage}/{Name}{Stage}.groovy (name could be empty)
- Fill the step with our changelog notification code: we can use CFG config object & MPL functions inside the steps definition
For this case, we will use “Deploy” step with path .jenkins/modules/Deploy/Deploy.groovy
.
We can check other standard steps on Jenkins Modular Pipeline Library repo: https://github.com/griddynamics/mpl/tree/master/resources/com/griddynamics/devops/mpl/modules
1. Configure some project-specific settings
@Library('jenkins-ci-automation@master') _ | |
YourPipeline([ | |
..... | |
configs: [ | |
serviceName: "Amazing Service", | |
postReleaseToTeams: true, | |
ticketingUrl: "https://example.com/browse/", | |
teamsWebhookUrl: "https://{company}.webhook.office.com/webhookb2/c0fd.../JenkinsCI/d89..." | |
] | |
]) |
ticketingUrl
is, like the name suggests, the base URL of your ticketing system or issue tracker e.g. Github Issues, Jira, etc.teamsWebhookUrl
is the webhook URL of your teams channel which will be notified with changelog posts.
2. Create the step file
Path: .jenkins/modules/Deploy/Deploy.groovy
We want to make sure the changelog is only posted to Teams on a successful release, and not during snapshot.
That means we need to execute extra logic after the original “Deploy” step.
Assuming params.RELEASE_TYPE
will have value RELEASE
on a release build, then:
MPLModule("Deploy") | |
script { | |
if (params.RELEASE_TYPE == 'RELEASE' && CFG.configs.postReleaseToTeams) { | |
.... | |
} | |
} |
3. Generate changelog & send notification to Teams
Next, we extract branch names from merge commits between previous tag and latest commit for further processing.
.... | |
def revisionRange = 'HEAD' | |
try { | |
def previousTag = sh( | |
script: 'git describe --tags --abbrev=0 `git rev-list --tags --skip=1 --max-count=1`', | |
returnStdout: true | |
).trim() | |
revisionRange = "$previousTag..HEAD" | |
} catch (Exception ignoredForSimplicity) {} | |
// Get the branches from the commit messages between the tags | |
def branches = [] | |
try { | |
branches = sh( | |
script: "/bin/bash -c 'git log $revisionRange --oneline | grep \"Merge\" | grep -Eo \"\\S*/?[A-Z]{2,}-[0-9]+\\S*\"'", | |
returnStdout: true | |
) | |
.trim() | |
.split('\n') | |
} catch (Exception ignoredForSimplicity) {} | |
.... |
branches
value would be something like:
branches = [ | |
'feature/PROJECT-1232', | |
'techdebt/PROJECT-1241-improve-query-performance', | |
'feature/PROJECT-1231', | |
'bugfix/PROJECT-1252-fix-intermittent-error', | |
'bugfix/PROJECT-1251', | |
'PROJECT-1301' | |
] |
We then group branches by their type (feature, techdebt, bugfix, etc).
..... | |
def groupedBranches = [:] | |
def otherKey = 'other' | |
branches.each { branch -> | |
def parts = branch.split('/', 2) | |
if (parts.size() > 1) { | |
def type = parts[0] | |
def ticket = parts[1] | |
if (!groupedBranches.containsKey(type)) { | |
groupedBranches[type] = [] | |
} | |
groupedBranches[type] << ticket | |
} else { | |
if (!groupedBranches.containsKey(otherKey)) { | |
groupedBranches[otherKey] = [] | |
} | |
groupedBranches[otherKey] << branch | |
} | |
} | |
.... |
groupedBranches
value would be something like:
groupedBranches = [ | |
feature: ['PROJECT-1232', 'PROJECT-1231'], | |
techdebt: ['PROJECT-1241-improve-query-performance'], | |
bugfix: ['PROJECT-1252-fix-intermittent-error', 'PROJECT-1251'], | |
etc: ['PROJECT-1301'] | |
] |
Next is generating the markdown for the body of the Teams post. We simply list the ticket number and the description under each type.
... | |
def changelogMarkdown = '' | |
groupedBranches.each { type, tickets -> | |
changelogMarkdown += "**${type.capitalize()}**\n" | |
tickets.eachWithIndex { value, index -> | |
def parts = value.split('-', 3) | |
def ticket = "${parts[0]}-${parts[1]}" | |
def description = '' | |
if (parts.size() == 3) { | |
description = parts[2].replaceAll("[-_]", " ") | |
} | |
changelogMarkdown += "${index + 1}. [$ticket](${CFG.configs.ticketingUrl}$ticket)" | |
if (description) { | |
changelogMarkdown += " - $description" | |
} | |
changelogMarkdown += "\n" | |
} | |
changelogMarkdown += '\n' | |
} | |
.... |
Then the generated markdown would be something like:
**Feature** | |
1. [PROJECT-1232](https://example.com/browse/PROJECT-1232) | |
2. [PROJECT-1231](https://example.com/browse/PROJECT-1231) | |
**Techdebt** | |
1. [PROJECT-1241](https://example.com/browse/PROJECT-1241) - improve query performance | |
**Bugfix** | |
1. [PROJECT-1252](https://example.com/browse/PROJECT-1252) - fix intermittent error | |
2. [PROJECT-1251](https://example.com/browse/PROJECT-1251) | |
**Etc** | |
1. [PROJECT-1301](https://example.com/browse/PROJECT-1301) |
Finally, post using office365 connector:
office365ConnectorSend( | |
message: "> # **$CFG.configs.serviceName $env.RELEASE_VERSION**\n\n$changelogMarkdown", | |
webhookUrl: CFG.configs.teamsWebhookUrl | |
) |
Result
For example above, once the release build is finished and deployed, Jenkins will automatically notify the changelog to the Ms Teams channel.
Amazing Service 1.2.0
Feature
Techdebt
- PROJECT-1241 - improve query performance
Bugfix
- PROJECT-1252 - fix intermittent error
- PROJECT-1251
Etc
This article show and example to generate a changelog and send it to a Teams channel. Using a similar method, we can send the changelog to almost anywhere with a HTTP API easily using curl or supported plugins.