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
60 changes: 54 additions & 6 deletions .github/actions/common/slack.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
#!/bin/bash
# Slack message builders for release notifications: a success message with the
# install command, or a failure message with the reason.
# Slack message builders and bot-token sender for release notifications.

# Post a Slack message via chat.postMessage. Reads the JSON payload from stdin.
# Usage: slack_send_with_token "$SLACK_BOT_TOKEN" < message.json
slack_send_with_token() {
local token="$1"
local curl_stderr; curl_stderr=$(mktemp)
if ! curl --fail-with-body -sS -X POST https://api.slack.com/api/chat.postMessage \
-H "Authorization: Bearer $token" \
-H "Content-Type: application/json; charset=utf-8" \
-d @- 2>"$curl_stderr"; then
echo "curl to chat.postMessage failed:" >&2
cat "$curl_stderr" >&2
rm -f "$curl_stderr"
return 1
fi
rm -f "$curl_stderr"
return 0
}

# Parse a chat.postMessage response (read from stdin). Writes slack_ts and
# slack_url to $GITHUB_OUTPUT when set. Non-zero exit on API error.
# Usage: slack_handle_message_result "$channel_id" "$sent_payload" < response.json
slack_handle_message_result() {
local channel_id="$1"
local message="$2"
local response; response=$(cat)

if echo "$response" | jq -e '.ok == true' >/dev/null; then
local slack_ts slack_channel ts_for_url slack_url
slack_ts=$(echo "$response" | jq -r '.ts')
slack_channel=$(echo "$response" | jq -r '.channel')
ts_for_url=$(echo "$slack_ts" | tr -d '.')
slack_url="https://redis.slack.com/archives/${slack_channel}/p${ts_for_url}"
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "slack_ts=$slack_ts" >> "$GITHUB_OUTPUT"
echo "slack_url=$slack_url" >> "$GITHUB_OUTPUT"
fi
echo "✅ Slack message sent: $slack_url"
return 0
fi

local error; error=$(echo "$response" | jq -r '.error // "unknown"')
echo "❌ Failed to send Slack message: $error" >&2
echo "$response" | jq '.' >&2
echo "Message content: $message" >&2
return 1
}

slack_format_success_message() {
# $1 release_tag $2 base_url (…/redis-cli) $3 env $4 footer
jq -n --arg tag "$1" --arg base "$2" --arg env "$3" --arg footer "$4" '
# $1 channel $2 release_tag $3 base_url (…/redis-cli) $4 env $5 footer
jq -n --arg channel "$1" --arg tag "$2" --arg base "$3" --arg env "$4" --arg footer "$5" '
{
"channel": $channel,
"icon_emoji": ":redis-circle:",
"text": (":redis-circle: redis-cli " + $tag + " published (" + $env + ")"),
"blocks": [
Expand All @@ -20,9 +67,10 @@ slack_format_success_message() {
}

slack_format_failure_message() {
# $1 release_tag $2 message $3 env $4 footer
jq -n --arg tag "$1" --arg msg "$2" --arg env "$3" --arg footer "$4" '
# $1 channel $2 release_tag $3 message $4 env $5 footer
jq -n --arg channel "$1" --arg tag "$2" --arg msg "$3" --arg env "$4" --arg footer "$5" '
{
"channel": $channel,
"icon_emoji": ":redis-circle:",
"text": (":x: redis-cli " + $tag + " release failed (" + $env + ")"),
"blocks": [
Expand Down
74 changes: 62 additions & 12 deletions .github/actions/slack-notification/action.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
name: "Slack notification"
description: "Send a redis-cli release Slack notification (no-op if the webhook secret is unset)"
description: "Send a redis-cli release Slack notification via a Slack App bot token (no-op if the bot token or channel is unset)"

inputs:
slack_func:
description: "slack_format_success_message | slack_format_failure_message"
required: true
slack_channel_id:
description: "Slack channel ID to post to (if empty, this action does nothing)"
required: false
default: ""
release_tag:
description: "Release tag"
required: true
Expand All @@ -19,27 +23,73 @@ inputs:
description: "Failure message"
required: false
default: ""
SLACK_WEB_HOOK_URL:
description: "Slack webhook URL (if empty, this action does nothing)"
SLACK_BOT_TOKEN:
description: "Slack App bot token (if empty, this action does nothing)"
required: false
default: ""

outputs:
slack_ts:
description: "Slack message timestamp"
value: ${{ steps.send-success.outputs.slack_ts || steps.send-failure.outputs.slack_ts }}
slack_url:
description: "Permalink to the Slack message"
value: ${{ steps.send-success.outputs.slack_url || steps.send-failure.outputs.slack_url }}

runs:
using: "composite"
steps:
- name: Send Slack notification
if: inputs.SLACK_WEB_HOOK_URL != ''
- name: Send success Slack notification
id: send-success
if: inputs.slack_func == 'slack_format_success_message' && inputs.SLACK_BOT_TOKEN != '' && inputs.slack_channel_id != ''
shell: bash
env:
SLACK_BOT_TOKEN: ${{ inputs.SLACK_BOT_TOKEN }}
run: |
set -e
. "${{ github.action_path }}/../common/slack.sh"
workflow_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
footer="Repo: ${{ github.repository }} | Commit: \`${{ github.sha }}\` | <$workflow_url|workflow run>"

msgfile="$(mktemp)"
slack_format_success_message \
"${{ inputs.slack_channel_id }}" \
"${{ inputs.release_tag }}" \
"${{ inputs.base_url }}" \
"${{ inputs.env }}" \
"$footer" > "$msgfile"

result="$(mktemp)"
if ! slack_send_with_token "$SLACK_BOT_TOKEN" < "$msgfile" > "$result"; then
echo "Failed to send message:"; cat "$msgfile"; exit 1
fi
slack_handle_message_result "${{ inputs.slack_channel_id }}" "$(cat "$msgfile")" < "$result"

- name: Send failure Slack notification
id: send-failure
if: inputs.slack_func == 'slack_format_failure_message' && inputs.SLACK_BOT_TOKEN != '' && inputs.slack_channel_id != ''
shell: bash
env:
SLACK_BOT_TOKEN: ${{ inputs.SLACK_BOT_TOKEN }}
run: |
set -e
. "${{ github.action_path }}/../common/slack.sh"
workflow_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
footer="Repo: ${{ github.repository }} | Commit: \`${{ github.sha }}\` | <$workflow_url|workflow run>"

if [ "${{ inputs.slack_func }}" = "slack_format_success_message" ]; then
payload="$(slack_format_success_message "${{ inputs.release_tag }}" "${{ inputs.base_url }}" "${{ inputs.env }}" "$footer")"
else
msg="${{ inputs.message }}"
[ -n "$msg" ] || msg=":x: redis-cli release failed"
payload="$(slack_format_failure_message "${{ inputs.release_tag }}" "$msg" "${{ inputs.env }}" "$footer")"
msg="${{ inputs.message }}"
[ -n "$msg" ] || msg=":x: redis-cli release failed"

msgfile="$(mktemp)"
slack_format_failure_message \
"${{ inputs.slack_channel_id }}" \
"${{ inputs.release_tag }}" \
"$msg" \
"${{ inputs.env }}" \
"$footer" > "$msgfile"

result="$(mktemp)"
if ! slack_send_with_token "$SLACK_BOT_TOKEN" < "$msgfile" > "$result"; then
echo "Failed to send message:"; cat "$msgfile"; exit 1
fi
printf '%s' "$payload" | curl -s --fail-with-body -d @- "${{ inputs.SLACK_WEB_HOOK_URL }}"
slack_handle_message_result "${{ inputs.slack_channel_id }}" "$(cat "$msgfile")" < "$result"
3 changes: 2 additions & 1 deletion .github/workflows/release_build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ jobs:
release_tag: ${{ needs.prepare-release.outputs.release_tag }}
env: ${{ inputs.release_type == 'public' && 'production' || 'staging' }}
message: ":x: redis-cli build failed"
SLACK_WEB_HOOK_URL: ${{ secrets.SLACK_WEB_HOOK_URL }}
slack_channel_id: ${{ vars.SLACK_CHANNEL_ID }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
7 changes: 5 additions & 2 deletions .github/workflows/release_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ name: Release Publish
# CLI_S3_IAM_ARN[_STAGING] IAM role to assume (public / internal)
# CLI_S3_BUCKET[_STAGING] target bucket (public / internal)
# CLI_S3_REGION[_STAGING] bucket region (public / internal)
# SLACK_WEB_HOOK_URL optional; release notifications
# SLACK_BOT_TOKEN optional; Slack App bot token for notifications
# Optional repository variable:
# SLACK_CHANNEL_ID channel to post release notifications to

on:
workflow_dispatch:
Expand Down Expand Up @@ -180,4 +182,5 @@ jobs:
env: ${{ inputs.release_type == 'public' && 'production' || 'staging' }}
base_url: ${{ inputs.release_type == 'public' && 'https://packages.redis.io/redis-cli' || format('https://{0}.s3.{1}.amazonaws.com/redis-cli', secrets.CLI_S3_BUCKET_STAGING, secrets.CLI_S3_REGION_STAGING) }}
message: ":x: redis-cli publish failed"
SLACK_WEB_HOOK_URL: ${{ secrets.SLACK_WEB_HOOK_URL }}
slack_channel_id: ${{ vars.SLACK_CHANNEL_ID }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}