Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions gh-actions/common/notify-mm-on-failure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Notify Mattermost on failure

Posts a workflow failure notification to a Mattermost channel using a bot account.

![Example Mattermost notification](example-notification.png)

## Requirements

The bot must be a member of the target channel and its token must be able to create posts. Store the token as a GitHub Actions secret. The Mattermost server URL and channel ID can be stored as repository or organization variables.

## Usage

```yaml
- name: Notify Mattermost on failure
if: ${{ failure() }}
uses: canonical/desktop-engineering/gh-actions/common/notify-mm-on-failure@main
with:
server-url: ${{ vars.MATTERMOST_SERVER_URL }}
channel-id: ${{ vars.MATTERMOST_CHANNEL_ID }}
bot-token: ${{ secrets.MATTERMOST_BOT_TOKEN }}
ping-users: alice,bob
additional-info: The package build did not complete.
request-ack: true
silent: true
priority: important
```

The action does not check the workflow result itself. Callers must use an appropriate condition, such as `failure()`.

## Inputs

| Input | Required | Default | Description |
| ----------------- | -------- | ------------ | -------------------------------------------------------------------------------------------- |
| `server-url` | Yes | | Base URL of the Mattermost server. |
| `channel-id` | Yes | | ID of the Mattermost channel to notify. |
| `bot-token` | Yes | | Access token for the Mattermost bot account. |
| `ping-users` | No | `""` | Comma-separated Mattermost usernames to mention in the notified users field. |
| `additional-info` | No | `""` | Markdown-formatted information to include in the notification. |
| `color` | No | `#FF0000` | Color of the notification attachment. |
| `author-icon` | No | `repo-owner` | URL of the attachment author icon. See [Author icon](#author-icon). |
| `request-ack` | No | `"true"` | Whether users must acknowledge the notification. |
| `silent` | No | `"false"` | Whether to deliver the post without user notifications or unread counts. |
| `priority` | No | `""` | Mattermost post priority. See [Priority and acknowledgement](#priority-and-acknowledgement). |

## Notification Behavior

### Priority and acknowledgement

Acknowledgement is requested by default, which marks the post as important. Set `request-ack` to `"false"` to send a notification without requiring acknowledgement.

Set `priority` to override the post priority. When it is empty, the action uses `important` if acknowledgement is requested and otherwise omits priority metadata.

### Silent posts

Set `silent` to `"true"` to post without desktop, push, or email notifications and without incrementing unread or mention counts.

### Author icon

The attachment author icon defaults to the calling repository owner's GitHub icon. Set `author-icon` to a URL to override it, or to an empty string (`author-icon: ""`) to omit the icon.

## API

The action calls the [Mattermost `POST /api/v4/posts` endpoint](https://developers.mattermost.com/api-documentation/#/operations/CreatePost).
166 changes: 166 additions & 0 deletions gh-actions/common/notify-mm-on-failure/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: Notify Mattermost on failure
description: Notify a Mattermost channel of a workflow failure using a bot account

inputs:
server-url:
description: The base URL of the Mattermost server.
required: true
channel-id:
description: The ID of the Mattermost channel to notify.
required: true
bot-token:
description: The access token for the Mattermost bot account.
required: true
ping-users:
description: A comma-separated list of Mattermost usernames to mention.
required: false
default: ""
additional-info:
description: Markdown-formatted additional information to include in the notification.
required: false
default: ""
color:
description: The color of the notification attachment.
required: false
default: "#FF0000"
author-icon:
description: >-
The URL of the notification attachment author icon. Use repo-owner to use
the calling repository owner's GitHub icon, or an empty string to omit it.
required: false
default: repo-owner
request-ack:
description: Whether users must acknowledge the notification.
required: false
default: "true"
silent:
description: Whether to deliver the notification without user notifications or unread counts.
required: false
default: "false"
priority:
description: The Mattermost post priority. Leave empty to infer it from request-ack.
required: false
default: ""

runs:
using: composite
steps:
- name: Notify Mattermost
shell: bash --noprofile --norc -euo pipefail {0}
env:
MM_SERVER_URL: ${{ inputs.server-url }}
MM_CHANNEL_ID: ${{ inputs.channel-id }}
MM_BOT_TOKEN: ${{ inputs.bot-token }}
MM_PING_USERS: ${{ inputs.ping-users }}
MM_ADDITIONAL_INFO: ${{ inputs.additional-info }}
MM_COLOR: ${{ inputs.color }}
MM_AUTHOR_ICON: ${{ inputs.author-icon }}
MM_REQUEST_ACK: ${{ inputs.request-ack }}
MM_SILENT: ${{ inputs.silent }}
MM_PRIORITY: ${{ inputs.priority }}
run: |
echo "::add-mask::${MM_BOT_TOKEN}"

case "${MM_REQUEST_ACK}" in
true) request_ack=true ;;
false) request_ack=false ;;
*)
echo "request-ack must be either 'true' or 'false'" >&2
exit 1
;;
esac

case "${MM_SILENT}" in
true) silent=true ;;
false) silent=false ;;
*)
echo "silent must be either 'true' or 'false'" >&2
exit 1
;;
esac

ping_list=''
IFS=',' read -ra users <<< "${MM_PING_USERS}"
for user in "${users[@]}"; do
user="${user#"${user%%[![:space:]]*}"}"
user="${user%"${user##*[![:space:]]}"}"
if [ -n "${user}" ]; then
ping_list="${ping_list} @${user}"
fi
done
ping_list="${ping_list# }"

author_icon="${MM_AUTHOR_ICON}"
if [ "${author_icon}" = "repo-owner" ]; then
author_icon="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY_OWNER}.png?size=16"
fi

# Prepare the JSON payload for the Mattermost post
payload=$(jq --null-input \
--arg channel_id "${MM_CHANNEL_ID}" \
--arg color "${MM_COLOR}" \
--arg author_name "${GITHUB_REPOSITORY} Action Failure" \
--arg author_link "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \
--arg author_icon "${author_icon}" \
--arg title "Workflow '${GITHUB_WORKFLOW}' failed" \
--arg title_link "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--arg additional_info "${MM_ADDITIONAL_INFO}" \
--arg event_name "${GITHUB_EVENT_NAME}" \
--arg ref "${GITHUB_REF}" \
--arg ref_link "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/tree/${GITHUB_REF}" \
--arg run_id "${GITHUB_RUN_ID}" \
--arg actor "${GITHUB_ACTOR}" \
--arg ping_list "${ping_list}" \
--arg priority "${MM_PRIORITY}" \
--argjson request_ack "${request_ack}" \
'{
channel_id: $channel_id,
props: {
attachments: [{
color: $color,
author_name: $author_name,
author_link: $author_link,
title: $title,
title_link: $title_link,
text: $additional_info,
fields: [
{short: true, title: "Event", value: $event_name},
{short: true, title: "Ref", value: "[\($ref)](\($ref_link))"},
{short: true, title: "Run ID", value: "[\($run_id)](\($title_link))"},
{short: true, title: "Actor", value: $actor},
{short: false, title: ":warning: Notified Users", value: $ping_list}
]
}]
}
}
| if $author_icon != "" then
.props.attachments[0].author_icon = $author_icon
else
.
end
| if $priority != "" or $request_ack then
.metadata.priority = {
priority: (if $priority != "" then $priority else "important" end),
requested_ack: $request_ack
}
else
.
end')

# Send the notification to Mattermost
response=$(curl --fail-with-body --silent --show-error --location \
--request POST \
--header "Authorization: Bearer ${MM_BOT_TOKEN}" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data-binary "${payload}" \
"${MM_SERVER_URL%/}/api/v4/posts?silent=${silent}")

# Check the response for a valid post ID
if ! post_id=$(jq --exit-status --raw-output \
'if (.id | type) == "string" and .id != "" then .id else empty end' \
<<< "${response}" 2>/dev/null); then
echo "::error::Mattermost returned an invalid response. Check that server-url is the Mattermost base URL and is accessible without an interactive login."
exit 1
fi
echo "Mattermost notification posted with ID ${post_id}"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading