Have you ever set up notifications from your loved CI/CD system to messenger apps? I received such a request recently, but I didn't take it seriously because most build systems these days have great integration with widespread messaging apps, especially Microsoft Teams.
I was in this nonchalant position until I read the developer's request thoroughly. They said:
Need to set up notifications (only tags notification, for only mentioned projects) to Teams channels about build results on Gitlab.
In other words, they wanted to receive notifications only for pipelines triggered by Git tags.
Well.
Immediately, I navigated to the configuration page in GitLab and selected a random project's integration section. Lo and behold, Microsoft Teams was available 😊.
Regarding the specific pipeline results the developer requested, GitLab offered an option for when A pipeline status changes. However, this could have been better because it would send notifications for all pipelines, regardless of whether they were triggered by tags, feature branches, or the mainline.
On the other hand, there is an option for when A tag is pushed to the repository or removed. However, this also doesn't suit our needs because it only sends notifications for actual Git pushes rather than the build pipeline situation.
I'll need to cook up a different approach to tackle this challenge. And to make this dish (spoiler alert: it's already in the oven), I'll be using the following ingredients:
– Microsoft Teams Webhook (an ordinary one)
– Adaptive Cards Generator (to add a touch of design flair to my code routine)
– Bash (same Bash we all beloved)
– GitLab Includes (to make all things better)
Microsoft Teams Webhook
Let's set up a Teams channel for receiving notifications. To get started:
Click on the three-dot icon to open the settings pop up.
From there, navigate to the Connectors item and search for Incoming Webhook. Create it! It's simple. Eventually you'll get the link like so:
https://mycorp.webhook.office.com/webhookb2/fe6f581b-c5ad-463b-b1f7-9d3897a321ee@5f98fadd-e7df-4de5-af15-f3da802bab2a/IncomingWebhook/1cxf37d5a64f4564ba6a294d161b8139a/8e1cxe4e-14b9-4910-b270-f9c0a2e0c822
Adaptive Cards Generator
We can send our notification as plain text, just like this:
John Doe has just pushed tag 0.4.0 of the React Frontend project, and the CI/CD pipeline has been completed successfully.
It's great if you're a big fan of minimalistic formatting and your coworkers also do appreciate it. However, if this doesn't apply, you should check out Adaptive Cards!
What is that? 🤔
Adaptive Cards are easy-to-use UI snippets that can be shared between apps and services. They're written in JSON and automatically adapt to their surroundings when displayed in an app. This makes it simple to create lightweight UI for diverse platforms and frameworks.
Microsoft created Adaptive Cards Designer to provide simple forms/snippets creating tools for their products, such as Teams, WPF, ReactNative and even Skype.
Click the link above and choose Microsoft Teams as the host app and Dark Theme as the color scheme (optional). Then, we can start designing our UI using the interface by directly editing the code. Then, depending on your distinctive requirements, you can achieve a final result comparable to my masterpiece:
This minimalistic and informative design shows the commit author, tag, and pipeline status.
You might think that you only need to copy the generated code and make a CURL request to Teams' webhook. However, that's not necessarily the case. When I tried it initially, I received a wrong HTTP response. There may be an issue with my Teams settings or policies that I couldn't resolve fully. Fortunately, I came across a case where the author encountered a similar problem and provided a working Adaptive Card template, which you can see below:
{
"type":"message",
"attachments":[
{
"contentType":"application/vnd.microsoft.card.adaptive",
"contentUrl":null,
"content":{
"$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
"type":"AdaptiveCard",
"version":"1.2",
"body":[
{
"type": "TextBlock",
"text": "For Samples and Templates, see [https://adaptivecards.io/samples](https://adaptivecards.io/samples)"
},
{
"type": "Image",
"url": "https://adaptivecards.io/content/cats/1.png"
}
]
}
}
]
}
To use this template, replace the body: []
part with the code you generated from the adaptivecards.io
website. The final result should look something like this:
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": null,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "My GitLab Project Path → Subpath → Project Name"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"style": "Person",
"url": "https://about.gitlab.com/images/press/press-kit-icon.svg",
"size": "Medium"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Author**: Maksim Muravev,
"isSubtle": false,
"wrap": true
},
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Tag**: 0.4.0",
"isSubtle": false,
"wrap": true
},
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Status**: SUCCESS ✅",
"isSubtle": false,
"wrap": true
},
],
"width": "stretch"
}
]
},
{
"type": "TextBlock",
"text": "Pipeline SUCCESS. See more.",
"wrap": true
}
]
}
}
]
}
As you can see from the example above, you can use markdown inside JSON fields to make the layout look more appealing.
Bash
To send this JSON payload to the webhook's URL and trigger Teams to message you, one can use the following Bash command:
curl -H "Content-Type: application/json" -d '<your JSON payload>' <webhook URL>
That's what we gonna do inside the GitLab CI pipeline.
GitLab Includes
This is the most essential and mind-bending part of the notification process.
We will use Bash command inside a GitLab CI pipeline to send notifications. However, before using it, we must substitute some data in the JSON payload, such as "Tag: 0.4.0", with actual data from the GitLab project.
Thankfully, GitLab provides predefined variables that we can use to access data like the tag, author, and project path via environmental variables inside a pipeline.
Substitution technique will be realized via cat << EOF >
concept to make code tidier due to quite massive JSON size.
So let's hypothetically imagine how the job can look alike:
teams-notification:
image: debian:bullseye
variables:
TEAMS_WEBHOOK_URL: "https://mycorp.webhook.office.com/webhookb2/fe6f581b-c5ad-463b-b1f7-9d3897a321ee@5f98fadd-e7df-4de5-af15-f3da802bab2a/IncomingWebhook/1cxf37d5a64f4564ba6a294d161b8139a/8e1cxe4e-14b9-4910-b270-f9c0a2e0c822"
script: |
cat << EOF > payload.json
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": null,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "${CI_PROJECT_PATH//\// → }"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"style": "Person",
"url": "https://about.gitlab.com/images/press/press-kit-icon.svg",
"size": "Medium"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Author**: ${GITLAB_USER_NAME}",
"isSubtle": false,
"wrap": true
},
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Tag**: : ${CI_COMMIT_REF_NAME}",
"isSubtle": false,
"wrap": true
},
{
"type": "TextBlock",
"spacing": "None",
"weight": "normal",
"text": "**Status**: ${STATUS^^} ${ICON}",
"isSubtle": false,
"wrap": true
},
],
"width": "stretch"
}
]
},
{
"type": "TextBlock",
"text": "Pipeline ${STATUS}. [See more](${CI_PIPELINE_URL}).",
"wrap": true
}
]
}
}
]
}
EOF
curl -H 'Content-Type: application/json' -d @payload.json $TEAMS_WEBHOOK_URL
Impressive. Let's break this example down into "molecules".
cat << EOF > payload.json
Here we append everything to the payload.json
file until EOF
happen.
"text": "${CI_PROJECT_PATH//\// → }"
Nicest one. I'm using Bash shell parameter expansion to replace a character in a variable. Initially, GitLab provides $CI_PROJECT_PATH
equals to main_project/sub_project/project_name
. Because it looks not user-friendly, I decided to replace each slash with a right arrow surrounded by spaces. It's sed-like syntax but inside variable expansion. After replacement it'll look much better: main_project → sub_project → project_name
.
"text": "Author: ${GITLAB_USER_NAME}"
Commit's author.
"text": "Tag: : ${CI_COMMIT_REF_NAME}"
Pushed tag by that's commit's author.
"text": "Status: ${STATUS^^} ${ICON}"
Getting the pipeline status can be thorny, especially since no GitLab predefined variable available to indicate the status. Additionally, determining the pipeline status within the pipeline itself becomes complicated 🤔. This means the pipeline is still unfinished (because of our job) to decide whether it succeeds or fails 🤔🤔🤔.
However, there is a solution 😎. It's based on two statements.
- We won't decide whether the pipeline succeeded or failed based on the entire pipeline termination status. We will accept the rule if one job is failed, so the whole pipeline is failed. It satisfies 99% percent of pipelines (by the way, if not, there is a workaround for it).
- We will use
rules.when
GitLab term to decide whether the previous job failed. And if it's failed (or not), the appropriate job will be triggered with arguments needed.
Yes, sounds complicated, but not really. See the example below.
GitLab Teams Notification Stage
Let's create the stage called notify-on-teams
. It must be latest in the pipeline to catch potential fails of previous ones.
.notification_template:
image: debian:bullseye
variables:
TEAMS_WEBHOOK_URL: "https://mycorp.webhook.office.com/webhookb2/fe6f581b-c5ad-463b-b1f7-9d3897a321ee@5f98fadd-e7df-4de5-af15-f3da802bab2a/IncomingWebhook/1cxf37d5a64f4564ba6a294d161b8139a/8e1cxe4e-14b9-4910-b270-f9c0a2e0c822"
script: |
cat << EOF > payload.json
{
"type": "message" ...
... # here is the rest of our job as above
teams_notification_when_pipeline_failed:
stage: notify_on_teams
extends:
- .notification_template
rules:
- when: on_failure
if: "$CI_COMMIT_TAG"
variables:
STATUS: "failed"
ICON: "❌"
teams_notification_when_pipeline_succeed:
stage: notify_on_teams
extends:
- .notification_template
rules:
- when: on_success
if: "$CI_COMMIT_TAG"
variables:
STATUS: "success"
ICON: "✅"
Yes, I use YAML anchors, but it's not rocket science. Just reusing the code.
As you can see, a final job runs if the previous job succeeds or fails. It runs only when the tag is pushed. Also, it conditionally (whether on_failure
or on_success
) passes the variable (status as plain text and emoji) to our main template.
And ta da! That's it. Hold on... do you remember word "Includes" in the header? We can make it more elegant using GitLab Includes. Why? To simplify process of adding all that snipets to each GitLab repositories which can be hundreds.
Includes
- Let's create a separate repository in GitLab called
ci-templates
. - Add files while maintaining a similar structure to this one: ```bash
.
└── teams
├── README.md
└── notifications.yaml
- In the `notification.yaml` file, define our pipeline and include all the code below it. Then commit and push the changes.
```yaml
.notification_template:
image: debian:bullseye
variables:
TEAMS_WEBHOOK_URL: "https://mycorp.webhook.office.com/webhookb2/fe6f581b-c5ad-463b-b1f7-9d3897a321ee@5f98fadd-e7df-4de5-af15-f3da802bab2a/IncomingWebhook/1cxf37d5a64f4564ba6a294d161b8139a/8e1cxe4e-14b9-4910-b270-f9c0a2e0c822"
script: |
cat << EOF > payload.json
{
"type": "message"
... # here is the rest of our job as above
.pipeline_failed:
extends:
- .notification_template
rules:
- when: on_failure
if: "$CI_COMMIT_TAG"
variables:
STATUS: "failed"
ICON: "❌"
.pipeline_succeed:
extends:
- .notification_template
rules:
- when: on_success
if: "$CI_COMMIT_TAG"
variables:
STATUS: "success"
ICON: "✅"
- Let's finally use the Include in child repositories: ```yaml
include:
- project: 'ci-templates'
ref: master
file:
- '/teams/notifications.yaml'
Don't forget about stages:
stages:
# - lint_python
# - test_python
# - build_docker
- teams_notifications # last stage
Finally, create two jobs and extend the templates:
teams_notifications_failed:
stage: teams_notifications
extends:
- .pipeline_failed
teams_notifications_success:
stage: teams_notifications
extends:
- .pipeline_succeed
That's it.
Top comments (1)
good