概要
GitHub Actionsを使ってCI/CDを構築すると、ビルド、テストが成功または失敗した時にSlackにメッセージを送りたい場合があります。今回のブログでは、GitHub Actionsを使ってSlackメッセージを送る方法について説明します。
Slackアプリの作成
Slackは対話型メッセージングプラットフォームで、メッセージを受け取るという意味は誰かがメッセージを送ったことを意味します。そのため、GitHub Actionsを使ってSlackメッセージを送るためには、Slackメッセージを送るSlackアプリを作成する必要があります。
まず、次のリンクをクリックしてSlack API
にアクセスします。
すると次の画面が表示されます。
画面右上のYour apps
を選択してアプリ作成画面に移動します。
Create New App
を選択して新しいアプリを生成します。
この時、From scratch
を選択して新しいアプリを生成します。
その後、アプリの名前を入力して、コナぷりを使うSlackのWorkspaceを選択してCreate App
ボタンをクリックします。
すると上記のようにアプリが生成されることを確認できます。
次はこのアプリにメッセージを送る権限を設定する必要があります。アプリ一覧画面で新しく作成したアプリの名前を選択してアプリ詳細ページに移動します。
その後、左メニューにあるOAuth & Permissions
を選択してOAuth & Permissions
画面に移動します。
少しスクロールしてScopes
セクションに移動し、Add an OAuth Scope
ボタンをクリックしてchat:write
権限を追加します。
最後にOAuth Tokens
セクションに移動して、Install to (workspace)
ボタンをクリックしてSlackアプリをインストールします。
アプリをインストールすると、次のようにBot User OAuth Access Token
が生成されることを確認できます。
次はこのように生成されたトークンをGitHub Actions
で使用するためにGitHub
のSecrets
に保存する必要があります。Slackメッセージを送りたいGitHub
リポジトリに移動します。
その後、Settings > Secrets and variables > Actions
メニューをクリックしてActions secrets and variables
ページに移動します。
画面に表示されたRepository secrets
のNew repository secret
ボタンをクリックして、Name
にSLACK_BOT_TOKEN
を入力し、Value
にSlack API
サイトで生成したトークンを入力してAdd secret
ボタンをクリックして保存します。
チャンネルID
GitHub Actions
を使ってSlackのチャンネルにメッセージを送るためにはCHANNEL_ID
が必要です。
CHANNEL_ID
はSlack
で取得できます。個人に直接送りたい場合は、次のように個人プロフィールからCopy member ID
をクリックしてCHANNEL_ID
を取得することができます。
もし、特定のチャンネルにメッセージを送りたい場合は、Open channel details
をクリックしてチャンネルの詳細情報に移動した後、
下に表示されたChannel ID
をコピーして使えば良いです。
slack-github-actionを使う
Slack
から公式で提供されているslack-github-action
を使うとGitHub Actions
からSlack
にメッセージを送ることができます。
- slack-github-action: https://github.com/slackapi/slack-github-action
GitHub Actions
を次のようにslack-github-action
を使うように修正するとSlack
にメッセージを送ることができます。
- name: Post to a Slack channel
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'CHANNEL_ID'
payload: |
{
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
}
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
簡単なメッセージを送る時にはこのslack-github-action
を使うと便利にSlack
メッセージを送ることができます。
Composite Action
会社ではslack-github-action
を使わず、次のようにComposite Action
を使ってSlack
メッセージを送る方法を使っています。
name: 'Send Slack messages'
description: 'Send Slack messages'
inputs:
GITHUB_TOKEN:
description: 'GitHub token to use GitHub API'
required: true
SLACK_BOT_TOKEN:
description: 'Token for Slack bot'
required: true
MESSAGES:
description: '(JSON) Multiple users and multiple Slack messages'
required: true
runs:
using: 'composite'
steps:
- name: Send Slack messages
uses: actions/github-script@v7
with:
github-token: ${{ inputs.GITHUB_TOKEN }}
script: |
const slackToken = process.env.SLACK_TOKEN
const messages = JSON.parse(Buffer.from(process.env.MESSAGES, 'base64').toString('utf-8'));
const channelIDs = {
'GITHUB_USER_NAME_1': 'USER_CHANNEL_ID_1',
'GITHUB_USER_NAME_2': 'USER_CHANNEL_ID_2',
'GITHUB_USER_NAME_2': 'USER_CHANNEL_ID_2',
'GITHUB_USER_NAME_3': 'USER_CHANNEL_ID_3',
'GITHUB_USER_NAME_3': 'USER_CHANNEL_ID_3',
'GITHUB_USER_NAME_4': 'USER_CHANNEL_ID_4',
}
for (const message of messages) {
const { userName, messages: blocks } = message
const channel = channelIDs[userName]
fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${slackToken}`,
},
body: JSON.stringify({
channel,
blocks,
})
})
.then(response => response.json())
.then(data => {
if (!data.ok) {
throw new Error(`Slack API error: ${data.error}`);
}
console.log('Message sent to Slack successfully');
})
.catch(error => {
console.error('Error sending message to Slack:', error);
});
}
env:
SLACK_TOKEN: ${{ inputs.SLACK_BOT_TOKEN }}
MESSAGES: ${{ inputs.MESSAGES }}
Composite Action
を使うとGitHub Actions
でSlack
メッセージを送る部分を共通化して使うことができます。Composite Action
については次のリンクを参考にしてください。
Reviewer通知
このように作ったComposite Action
を使ってGitHub Actions
を使ってReviewerに指定された場合、Slack
メッセージを送る方法について説明します。
GitHub Actions
でReviewerに指定された場合、Slack
の個人チャンネルにメッセージを送るためには次のようにGitHub Actions
を作成することができます。
name: '[Slack] Reviewer assigned'
on:
pull_request:
types: [review_requested]
jobs:
notify_for_reviewer:
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Make Slack messages
id: make-slack-messages
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const prTitle = context.payload.pull_request.title;
const prLink = context.payload.pull_request.html_url;
const { data: reviewers } = await github.rest.pulls.listRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
let slackMessages = []
const reviewerLogins = reviewers.users.map(user => user.login);
for (const userName of reviewerLogins) {
const message = `*Reviewer notification*\n\nYou are assigned to new PR.\n\n- title: ${prTitle}\n- link: ${prLink}`
slackMessages.push({
userName,
messages: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: message,
}
}
]
})
}
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
- name: Send Slack messages
uses: ./.github/actions/send_slack_messages
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
このGitHub Actions
をもっと詳しく見ていきます。
name: '[Slack] Reviewer assigned'
on:
pull_request:
types: [review_requested]
...
このGitHub Actions
はpull_request
イベントのreview_requested
を使ってPRにReviewerが指定された場合にのみ実行されます。
...
steps:
- name: Checkout Repository
uses: actions/checkout@v4
...
Composite Action
はGit
から管理されているため、actions/checkout@v4
を使ってリポジトリをチェックアウトする必要があります。
...
- name: Make Slack messages
id: make-slack-messages
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const prTitle = context.payload.pull_request.title;
const prLink = context.payload.pull_request.html_url;
const { data: reviewers } = await github.rest.pulls.listRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
let slackMessages = []
const reviewerLogins = reviewers.users.map(user => user.login);
for (const userName of reviewerLogins) {
const message = `*Reviewer notification*\n\nYou are assigned to new PR.\n\n- title: ${prTitle}\n- link: ${prLink}`
slackMessages.push({
userName,
messages: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: message,
}
}
]
})
}
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
actions/github-script@v7
を使ってReviewerにメッセージを送るためのメッセージを作成します。PRのタイトル、リンクを取得してReviewer用のメッセージを作成します。
Reviewer notification
You are assigned to new PR.
- title: PR title
- link: https://...
このメッセージはPRに指定されたReviewer全員にメッセージを送ることになります。
...
const reviewerLogins = reviewers.users.map(user => user.login);
for (const userName of reviewerLogins) {
...
このように作ったメッセージをJSON
をそのまま渡すと問題が発生します。そのため、Buffer
を使ってbase64
にエンコードしてcore.setOutput
を使ってMESSAGES
に保存して渡すようにしました。
...
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
最後は、前に作ったComposite Action
を使ってSlack
メッセージを送りました。
...
- name: Send Slack messages
uses: ./.github/actions/send_slack_messages
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
...
毎朝メッセージ送信
レビュワーがSlackメッセージを受け取った後、PRをレビューする場合もありますが、忙しくてPRをレビューできない場合もあります。このような場合、毎朝レビュワーにメッセージを送ってPRをレビューするように促すことができます。
次のようにGitHub Actions
を作成すると、平日の朝(月〜金)9時30分(日本時間)にレビュワーにメッセージを送ることができます。
name: '[Slack] Every weekday at 9:30 AM'
on:
schedule:
- cron: '30 0 * * 1-5'
jobs:
notify_reviewers_every_weekday:
runs-on: ubuntu-latest
timeout-minutes: 1
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Make Slack messages
id: make-slack-messages
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const userList = [
'GITHUB_USER_NAME_1',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_4',
]
const escapeForSlack = (text) => {
return text
.replace(/&/g, '&') // & → &
.replace(/</g, '<') // < → <
.replace(/>/g, '>') // > → >
.replace(/"/g, '"') // " → "
.replace(/'/g, '''); // ' → '
}
// Get All PRs
const prList = []
let pageIndex = 1
let hasMorePages = true
while (hasMorePages) {
const result = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
page: pageIndex,
})
if (result.data.length > 0) {
prList.push(...result.data)
pageIndex += 1
} else {
hasMorePages = false
}
}
let slackMessages = []
for (const userName of userList) {
const reviewerPRList = prList.filter(pr => pr.user.login !== 'dependabot[bot]' &&
pr.requested_reviewers.some(reviewer => reviewer.login === userName)
);
if (reviewerPRList.length > 0) {
let message = '*Reviewer notification*\n\nYou have assigned PRs. Please review when you have time.\n'
reviewerPRList.forEach(pr => {
message += `\n- <${pr.html_url}|${escapeForSlack(pr.title)}>`;
});
slackMessages.push({
userName,
messages: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: message,
}
}
]
})
}
}
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
- name: Send Slack messages
uses: ./.github/actions/send_slack_messages
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
このGitHub Actions
をもっと詳しく見ていきます。
name: '[Slack] Every weekday at 9:30 AM'
on:
schedule:
- cron: '30 0 * * 1-5'
...
このGitHub Actions
はschedule
を使って平日(1-5)の朝9時30分(日本時間)に実行されます。
...
steps:
- name: Checkout Repository
uses: actions/checkout@v4
...
Composite Action
はGit
から管理されているため、actions/checkout@v4
を使ってリポジトリをチェックアウトする必要があります。
...
- name: Make Slack messages
id: make-slack-messages
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const userList = [
'GITHUB_USER_NAME_1',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_4',
]
const escapeForSlack = (text) => {
return text
.replace(/&/g, '&') // & → &
.replace(/</g, '<') // < → <
.replace(/>/g, '>') // > → >
.replace(/"/g, '"') // " → "
.replace(/'/g, '''); // ' → '
}
// Get All PRs
const prList = []
let pageIndex = 1
let hasMorePages = true
while (hasMorePages) {
const result = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
page: pageIndex,
})
if (result.data.length > 0) {
prList.push(...result.data)
pageIndex += 1
} else {
hasMorePages = false
}
}
let slackMessages = []
for (const userName of userList) {
const reviewerPRList = prList.filter(pr => pr.user.login !== 'dependabot[bot]' &&
pr.requested_reviewers.some(reviewer => reviewer.login === userName)
);
if (reviewerPRList.length > 0) {
let message = '*Reviewer notification*\n\nYou have assigned PRs. Please review when you have time.\n'
reviewerPRList.forEach(pr => {
message += `\n- <${pr.html_url}|${escapeForSlack(pr.title)}>`;
});
slackMessages.push({
userName,
messages: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: message,
}
}
]
})
}
}
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
actions/github-script@v7
を使ってReviewerにメッセージを送るためのメッセージを作成します。各GitHubユーザーが割り当てられたすべてのPRを取得して、レビュワーに送るメッセージを作成します。
Reviewer notification
You have assigned PRs. Please review when you have time.
- PR title1: https://...
- PR title2: https://...
- PR title3: https://...
- PR title4: https://...
このメッセージはすでに作成したGitHubユーザーのリストにある全てのユーザーにメッセージを送ることになります。
...
const userList = [
'GITHUB_USER_NAME_1',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_2',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_3',
'GITHUB_USER_NAME_4',
]
...
このように作ったメッセージをJSON
をそのまま渡すと問題が発生します。そのため、Buffer
を使ってbase64
にエンコードしてcore.setOutput
を使ってMESSAGES
に保存して渡すようにしました。
...
const encodedMessages = Buffer.from(JSON.stringify(slackMessages)).toString('base64');
core.setOutput('MESSAGES', encodedMessages);
...
最後は、前に作ったComposite Action
を使ってSlack
メッセージを送りました。
...
- name: Send Slack messages
uses: ./.github/actions/send_slack_messages
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
MESSAGES: ${{ steps.make-slack-messages.outputs.MESSAGES }}
...
完了
これでGitHub Actions
を使ってReviewerに指定された場合、Slack
メッセージを送る方法と、毎朝レビュワーにPRリストを送る方法について説明しました。
レビュワーに指定されたことが認識されなくて、レビューが遅れている場合、この方法を使ってレビュワーにメッセージを送ってレビューを促してみてください。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。