notifications cicd cycle - juancamilocc/virtual_resources GitHub Wiki
In this guide, you will learn how to add notifications in a CI/CD cycle using Jenkins, through email, Teams and Slack, as an additional step.
NOTE: We will buil upon the previous guide Deploying our first application in Kubernetes with a CI/CD cycle using Jenkins and ArgoCD, where we deployed a Golang application.
You can view the general workflow here.
First of all, we must verify that the Email Extension Plugin
is installed.
We need to create credentials for our SMTP server. Go to your user, click on Credentials
, then navigate to Stores from parent > System > Global credentials (unrestricted) > Add Credentials
.
Select Username and Password
type.
Now, go to Manage Jenkins > System
and find the Extended E-mail Notification
section. Set the SMTP server, select the credentials and check the Use TLS
.
NOTE: The previous example uses an Outlook SMTP server, but you can use other services, like Gmail (smtp.gmail.com), Yahoo (smtp.mail.yahoo.com), iCloud Mail Apple (smtp.mail.me.com) among others.
To use this notification, it's only necessary to include it in the post-action of our pipeline.
pipeline {
agent {
kubernetes {
.
.
.
}
}
.
.
.
stage {
.
.
.
}
post {
success {
//Send email notification
emailext (
subject: "${JOB_NAME} build",
body: "The ${JOB_NAME} with ${BUILD_NUMBER} build was successful",
to: "<destinatarie1>, <destinatarie2>, ...",
from: "<yourEmailSMTPServer>",
attachmentsPattern: "<file1>, <file2>, ..."
)
}
failure {
//Send email notification
emailext (
subject: "${JOB_NAME} build",
body: "The ${JOB_NAME} with ${BUILD_NUMBER} build failed",
to: "<destinatarie1>, <destinatarie2>, ...",
from: "<yourEmailSMTPServer>",
attachmentsPattern: "<file1>, <file2>, ..."
)
}
}
}
NOTE: You can find more information about this plugin here.
In this section, we will see two options for sending notifications via Teams, using the plugin Office 365 Connector and Workflows.
First we must create the Workflow. Go to search the Workflows
app and click on it, after that New Flow
.
In Search template
set webhook
and select us the first Post to a channel when a webhook request is received
option.
Set a flow name, then click on Next > Create flow
. You will get a webhook, as follows.
Inside the Workflows, go to Home
. There you will be able to see your flow, click on it.
In the Connections
section, click on Edit
, then select Add a user or group as owner
and search the admin user. We must change it because this will appear in the notifications.
Click on Manage connections
and swicth the default account.
Go back to Home in Workflows, enter our flow and click on Edit
. The following screen will appear.
Click over Send each adaptative card > Post card in a chat or channel
, select Channel or Group chat
and click on Save, as follows.
We must copy the webhook, click on When a Teams webhook request is received
and copy the URL.
Now, in Jenkins, create the webhook credentials. Go to your user, click on Credentials
, in the Stores from parent > System > Global credentials (unrestricted) > Add Credentials
.
Select Secret Text
type.
We must define the adaptative card json file, as follows.
{
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "${title}",
"wrap": true
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"style": "Person",
"url": "https://ftp-chi.osuosl.org/pub/jenkins/art/jenkins-logo/1024x1024/logo.png",
"altText": "Jenkins Notification",
"size": "Small"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"weight": "Bolder",
"text": "Jenkins Notification",
"wrap": true
},
{
"type": "TextBlock",
"spacing": "None",
"text": "${date}",
"isSubtle": true,
"wrap": true
}
],
"width": "stretch"
}
]
},
{
"type": "TextBlock",
"weight": "Bolder",
"color": "${colorStatus}",
"text": "${status}",
"wrap": true
},
{
"type": "FactSet",
"facts": [
{
"title": "Author",
"value": "${author}"
},
{
"title": "Last Merge/Commit",
"value": "${lastChange}"
},
{
"title": "Tag project",
"value": "${tagProject}"
}
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Ver compilación en Jenkins",
"url": "${urlJenkinsLogs}"
}
]
}
}
]
}
The previous file should be saved in the repository with the name requestTeamsNotifications.json
or the name that you want to assign it. Now we can use it in the pipieline, as follows.
pipeline {
agent {
kubernetes {
.
.
.
}
}
.
.
.
environment {
EMAIL_USER = sh(script: 'git log -1 --pretty="%ae"', returnStdout: true).trim()
LAST_CHANGE = sh(script: 'git log -1 --name-status --pretty=format:"%h %s"', returnStdout: true).trim()
DATE = sh(script: 'TZ="America/Bogota" date "+%Y-%m-%d-%H-%M-%S"', returnStdout: true).trim()
IMAGE_TAG = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
}
stage {
.
.
.
}
post {
success {
script {
//Send teams notification with workflows
withCredentials([string(credentialsId: 'teams-notifications', variable: 'webhook')]) {
def requestTeams = readFile('requestTeamsNotifications.json')
requestTeams = requestTeams.replace('${title}', "La compilación de <project_name> fue exitosa, desplegando cambios...")
requestTeams = requestTeams.replace('${date}', "${DATE}")
requestTeams = requestTeams.replace('${status}', 'SUCCESS')
requestTeams = requestTeams.replace('${colorStatus}', 'good')
requestTeams = requestTeams.replace('${author}', "${EMAIL_USER}")
requestTeams = requestTeams.replace('${lastChange}', "${LAST_CHANGE}")
requestTeams = requestTeams.replace('${tagProject}', "image_docker:${IMAGE_TAG}-${DATE}-<environment>")
requestTeams = requestTeams.replace('${urlJenkinsLogs}', "${currentBuild.absoluteUrl}")
writeFile file: 'requestTeamsNotifications.json', text: requestTeams
sh 'curl -H "Content-Type: application/json" -d @requestTeamsNotifications.json $webhook'
}
}
}
failure {
script {
// Send teams notification with workflows
withCredentials([string(credentialsId: 'teams-notifications', variable: 'webhook')]) {
def requestTeams = readFile('requestTeamsNotifications.json')
requestTeams = requestTeams.replace('${title}', "La compilación de <project_name> fallo!")
requestTeams = requestTeams.replace('${date}', "${DATE}")
requestTeams = requestTeams.replace('${status}', 'FAILURE')
requestTeams = requestTeams.replace('${colorStatus}', 'attention')
requestTeams = requestTeams.replace('${author}', "${EMAIL_USER}")
requestTeams = requestTeams.replace('${lastChange}', "${LAST_CHANGE}")
requestTeams = requestTeams.replace('${tagProject}', 'N/A')
requestTeams = requestTeams.replace('${urlJenkinsLogs}', "${currentBuild.absoluteUrl}")
writeFile file: 'requestTeamsNotifications.json', text: requestTeams
sh 'curl -H "Content-Type: application/json" -d @requestTeamsNotifications.json $webhook'
}
}
}
}
}
This will allow us to obtain the following notifications in both success and failure cases.
Success case
Failure case
NOTE: You can customize the adaptative card .json file with the values you want; you just need to add more items in the FactSet
section.
"type": "FactSet",
"facts": [
{
"title": "<nameFact>",
"value": "<valueFact>"
},
{
"title": "<nameFact2>",
"value": "<valueFact2>"
},
.
.
.
]
First, we must install the plugin in our Jenkins server. Go to Manage jenkins > Plugins > Available Plugins
.
Now, go to Teams, search for the channel that we want to associate, and access its options.
Click on connector
option.
Search Jenkins
and click on configure
.
Set a name, click on create
.
This will generate a webhook.
Now, go to the Jenkins server, we will save the obtained webhook as credentials and use it in the pipelines. Go to your user, click on Credentials
, in the Stores from parent > System > Global credentials (unrestricted) > Add Credentials
.
Select Secret Text
type.
Now, we can use it in the pipeline, as follows.
pipeline {
agent {
kubernetes {
.
.
.
}
}
.
.
.
environment {
EMAIL_USER = sh(script: 'git log -1 --pretty="%ae"', returnStdout: true).trim()
LAST_CHANGE = sh(script: 'git log -1 --name-status --pretty=format:"%h %s"', returnStdout: true).trim()
}
stage {
.
.
.
}
post {
success {
//Send teams notification
withCredentials([string(credentialsId: 'token-teams', variable: 'TOKEN_TEAMS')]) { // Token from Workflows Teams
office365ConnectorSend (
office365ConnectorSend adaptiveCards: true,
message: "## La compilación de ${currentBuild.projectName} fue exitosa.\n",
factDefinitions: [
[name: "Author", template: "Cambios realizados por: ${EMAIL_USER}\n"],
[name: "Commits", template: "\n${LAST_CHANGE}"]
],
status: "SUCCESS",
webhookUrl: "${TOKEN_TEAMS}"
)
}
}
failure {
//Send teams notification
withCredentials([string(credentialsId: 'token-teams', variable: 'TOKEN_TEAMS')]) {
office365ConnectorSend (
office365ConnectorSend adaptiveCards: true,
message: "## La compilación de ${currentBuild.projectName} falló.\n",
factDefinitions: [
[name: "Author", template: "Cambios realizados por: ${EMAIL_USER}\n"],
[name: "Commits", template: "\n${LAST_CHANGE}"]
],
status: "FAILURE",
webhookUrl: "${TOKEN_TEAMS}"
)
}
}
}
}
This will allow us to obtain the following notification.
Go to Slack, click on Automatizaciones
.
In Aplicaciones
section search Jenkins, click on Añadir
Add to Slack and select the channel that we want to use.
Scroll down and read the instructions. There you can find the subdomain
and integration token
, save them.
We must install the plugin in our Jenkins server, go to Manage Jenkins > Plugins > Available plugins
and search slack
.
Create the secret credentials with the integration token. Go to your user, click on Credentials
, in the Stores from parent > System > Global credentials (unrestricted) > Add Credentials
.
Now, let's set up the plugin. Go to Manage Jenkins > System
, search for the Slack
section, in Workspace
field set the subdomain and select the credentials that we have created.
Finally you can use it in the pipeline, as follows.
pipeline {
agent {
kubernetes {
.
.
.
}
}
.
.
.
environment {
EMAIL_USER = sh(script: 'git log -1 --pretty="%ae"', returnStdout: true).trim()
LAST_CHANGE = sh(script: 'git log -1 --name-status --pretty=format:"%h %s"', returnStdout: true).trim()
DATE = sh(script: 'TZ="America/Bogota" date "+%Y-%m-%d-%H-%M-%S"', returnStdout: true).trim()
IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
}
stage {
.
.
.
}
post {
success {
// Slack notifications
slackSend(
channel: 'jenkins-notifications',
color: '#00FF00',
message: "Build of <project_name> was successful!",
attachments: [
[
title: "Build of <project_name> was successful!",
text: "Build details",
fields: [
[title: "Date", value: "${DATE}", short: true],
[title: "Status", value: "Success", short: true],
[title: "Changes made by", value: "${EMAIL_USER}", short: true],
[title: "Last Merge/commit", value: "${LAST_CHANGE}", short: true],
[title: "Project Tag", value: "rps-game:${IMAGE_TAG}-${DATE}-<environment>", short: true]
],
footer: "Jenkins",
ts: env.BUILD_TIMESTAMP,
color: "#36a64f"
]
]
)
}
failure {
// Slack notifications
slackSend (
channel: 'jenkins-notifications',
color: '#00FF00',
message: "Build of <project_name> failed!",
attachments: [
[
title: "Build of <project_name> failed!",
text: "Build details",
fields: [
[title: "Date", value: "${DATE}", short: true],
[title: "Status", value: "Failure", short: true],
[title: "Changes made by", value: "${EMAIL_USER}", short: true],
[title: "Last Merge/commit", value: "${LAST_CHANGE}", short: true]
],
footer: "Jenkins",
ts: env.BUILD_TIMESTAMP,
color: "#ff0000"
]
]
)
}
}
}
This will allow us to obtain the following notifications in both success and failure cases.
Success case
Failure case
NOTE: Based on my personal experience. I recommend using Teams
or Slack
notifications for succes cases, while also including email notifications for failure cases to send a file with error logs.
You can do this, for example, in the build
stage.
stage('Build and Push image') {
steps {
container('<pod_container>') {
script {
withCredentials([usernamePassword(credentialsId: 'credentials-dockerhub', usernameVariable: 'DOCKERHUB_USERNAME', passwordVariable: 'DOCKERHUB_PASSWORD')]) {
sh '''
echo $DOCKERHUB_PASSWORD | docker login -u $DOCKERHUB_USERNAME --password-stdin
docker build -t juancamiloccc/rps-game:$IMAGE_TAG-$DATE-staging . 2> logs-docker.txt // Here save the logs in a file
docker push juancamiloccc/rps-game:$IMAGE_TAG-$DATE-staging
'''
}
}
}
}
post {
success {
.
.
.
}
failure {
emailext (
subject: "...",
body: "...",
to: "...",
from: "...",
attachmentsPattern: "logs-docker.txt" // Here send it to email
)
}
}
You learned how to configure email, Teams, and Slack notifications from Jenkins. Having a notification system in your CI/CD cycle is crucial; it keeps the entire team informed and enables quick decision-making and actions. Effective notifications help to identify and resolve issues promptly, thereby improving the overall efficiency of the development process. In the next guide Linters and Formatters in CI/CD cycle, we will discuss how to incorporate linters and formatters for static code analysis.