notifications cicd cycle - juancamilocc/virtual_resources GitHub Wiki

Add Notifications in CI/CD cycle

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.

general workflow

Email notifications configuration

First of all, we must verify that the Email Extension Plugin is installed.

Email plugin

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.

credentials SMTP

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.

Email configuration

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.

Teams notifications configuration

In this section, we will see two options for sending notifications via Teams, using the plugin Office 365 Connector and Workflows.

Using Workflows

First we must create the Workflow. Go to search the Workflows app and click on it, after that New Flow.

Workflows app

In Search template set webhook and select us the first Post to a channel when a webhook request is received option.

Workflows app

Set a flow name, then click on Next > Create flow. You will get a webhook, as follows.

Workflows app

Inside the Workflows, go to Home. There you will be able to see your flow, click on it.

Workflows app

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.

Workflows app

Click on Manage connections and swicth the default account.

Workflows app

Go back to Home in Workflows, enter our flow and click on Edit. The following screen will appear.

Workflows app

Click over Send each adaptative card > Post card in a chat or channel, select Channel or Group chat and click on Save, as follows.

Workflows app

We must copy the webhook, click on When a Teams webhook request is received and copy the URL.

Workflows app

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.

Workflows app

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

Notification using workflows

Failure case

Notification using workflows

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>"
                },
                .
                .
                .
            ]

Using Office 365 Connector

First, we must install the plugin in our Jenkins server. Go to Manage jenkins > Plugins > Available Plugins.

Office 365 connector plugin

Now, go to Teams, search for the channel that we want to associate, and access its options.

Click on connector option.

Connector channel

Search Jenkins and click on configure.

Connector jenkins

Set a name, click on create.

Connector config

This will generate a webhook.

Webhook connector

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.

Credentials Office 365 plugin

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.

Teams notification

Slack notifications configuration

Go to Slack, click on Automatizaciones.

Slack configuration

In Aplicaciones section search Jenkins, click on Añadir

Slack configuration

Add to Slack and select the channel that we want to use.

Slack configuration Slack configuration

Scroll down and read the instructions. There you can find the subdomain and integration token, save them.

Slack configuration

We must install the plugin in our Jenkins server, go to Manage Jenkins > Plugins > Available plugins and search slack.

Slack configuration

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.

Slack configuration

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.

Slack configuration

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

Slack configuration

Failure case

Slack configuration

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
        )
    }
}

Conclusions

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.

⚠️ **GitHub.com Fallback** ⚠️