개요
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
저장소(Repository)로 이동합니다.
그 다음 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에 지정된 Reviewers 모두에게 메시지를 보내게 됩니다.
...
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
는 평일(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
를 사용하여 리뷰어에 지정된 경우 Slack 메시지를 보내는 방법과 매일 아침에 리뷰어에게 PR 목록을 Slack 메시지로 발송하는 방법에 대해서 알아보았습니다.
리뷰어에 지정된 것이 인식되지 않아서 리뷰가 계속 지연되는 문제가 있는 경우, 이러한 방법을 사용하여 리뷰어에게 메시지를 보내어 리뷰를 유도해 보시기 바랍니다.
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku
가 개발한 앱을 한번 사용해보세요.Deku
가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.