はじめに
組織のGitHubで新しいリポジトリが作成された時にSlack通知する仕組みをGitHub Actionsで作りました。
通知はリアルタイムではなく、前日に作成されたリポジトリが次の日に通知される仕組みです。
- 前日にリポジトリが作成された場合、Slackで作成通知が飛ぶ
- 前日にリポジトリが作成されなかった場合、Slack通知は行わない
- エラー時は、Slackに失敗通知が飛ぶ
検討した実装方式の比較と、実際のGitHub Actionsでの実装を紹介します。
検討した実装方式の比較
方法①GitHub Webhook⇒Slack Incoming Webhook(変換処理なしでは接続できない)
Webhook単体では形式の違いで実装できませんでした。
GitHub WebhookはペイロードをそのままSlack Incoming Webhookへ送信しますが、Slack Incoming Webhookは「Slack用の決まったJSON形式」でないといけないためです。そのため、途中で文面を整形する処理を挟む必要があります。
方法②LambdaやStep Functionsを間に挟む方法
通知を整形する処理を挟む必要があると分かったため、以下のようなLambdaやStep Functionsを挟む方法も検討しました。
- GitHub Webhook ⇒ Lambda・Step Functions
- GitHub App ⇒ Lambda・Step Functions
- GitHub Actions ⇒ Step Functions
この方法でも実装は可能ですが、新たに構築・運用する手間が増えるのと、GitHub Actionsのみでシンプルに実装できそうだったため見送りました。
方法③GitHub Actionsのみ(採用)
GitHub Actionsは、scheduleトリガーによる定期実行と、その中でのスクリプト実行が可能です。そのため、リポジトリ一覧の取得から通知までの一連の処理をGitHub Actions単体で完結させられます。LambdaやStep Functionsのような外部インフラを新たに構築・運用する必要がなく、また方法①で課題だった「GitHub WebhookのイベントJSONとSlackが求めるJSON形式の違い」も、Actions内のjqで整形できました。
実際の実装
方法③GitHub Actionsで実装した内容をまとめます。
環境変数の設定
3つの値をGitHub ActionsのSecretsまたはVariablesに登録します。
トークンやWebhook URLのような機密情報はsecrets、組織名のような非機密情報はvarsから参照します。
①組織をリソースオーナーに指定したFine-grained personal access token(Pat)を設定する
※組織側でFine-grained PATの承認が必要な場合があるため、確認してからの利用をおすすめします。
-
GitHubの右上のアイコン>「Settings」に遷移する
-
左メニューの「Developer settings」を選択する
-
「Personal access tokens」>「Fine-grained tokens」を選択する
-
「Token name」に任意の名前を入力する
-
「Resource owner」で対象の組織を選択する
-
「Repository access」で「All repositories」を選択する
-
「Permissions」の「Repository permissions」から「Metadata」を「Read-only」に設定する

-
「Generate token」を押下してトークンを発行する
-
発行したトークンをコピーし、GitHub Actionsを実装するリポジトリの「Settings」>「Secrets and variables」>「Actions」>「Secrets」タブで
ORGANIZATION_REPOSITORY_PATなどのシークレット名として登録する

②Slack Webhook URLを設定する
- https://api.slack.com/apps にアクセスし、組織で運用しているSlack Appを選択する(ない場合は新規追加)
- 左メニューの「Incoming Webhooks」を選択する
- 通知したいチャンネル用のWebhook URL(
https://hooks.slack.com/services/...)をコピーする(未発行の場合は「Add New Webhook to Workspace」から発行する) - GitHub Actionsを実装するリポジトリの「Settings」>「Secrets and variables」>「Actions」>「Secrets」タブで
SLACK_WEBHOOK_URLなどのシークレットとして登録する
※my.slack.com/services経由で作成できる旧式の「Legacy custom integrations: Incoming Webhooks」は非推奨となっており、Slack側にも廃止予定である旨の警告が表示されるため使用しないでください。
③組織のリポジトリ名を設定する
- GitHub Actionsを実装するリポジトリの「Settings」に遷移する
- 左メニューの「Secrets and variables」>「Actions」を押下する
- 「Variables」タブを押下する
- 「New repository variable」を押下する
- Nameに
ORG_NAME、Valueに通知対象の組織名を入力し、「Add variable」を押下する
GitHub Actionsワークフローの作成
yamlファイルを.github/workflows/配下に配置する
(例:.github/workflows/ファイル名.yaml)
今回は以下のようなコードを配置しました。
name: repository-create-detection
on:
schedule:
- cron: '0 0 * * *' # UTC 00:00 = JST 09:00
workflow_dispatch:
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Check newly created repositories and notify Slack
env:
GH_TOKEN: ${{ secrets.ORGANIZATION_REPOSITORY_PAT }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
ORG_NAME: ${{ vars.ORG_NAME }}
run: |
set -Eeuo pipefail
send_slack() {
local text="$1"
curl -sSf -X POST -H 'Content-Type: application/json' \
--data "$(jq -n --arg text "$text" '{text: $text}')" \
"$SLACK_WEBHOOK_URL"
}
on_error() {
local exit_code=$?
trap - ERR
send_slack "<!here> GitHubリポジトリ通知ワークフローが失敗しました。" || true
exit "$exit_code"
}
trap on_error ERR
# JST基準で「前日 00:00:00 以上 24:00:00 未満」のUTC境界を作る
START_UTC=$(TZ=Asia/Tokyo date -d 'yesterday 00:00:00' -u '+%Y-%m-%dT%H:%M:%SZ')
END_UTC=$(TZ=Asia/Tokyo date -d 'today 00:00:00' -u '+%Y-%m-%dT%H:%M:%SZ')
# 組織のリポジトリを作成日時降順で取得
REPOS=$(curl -sSf -H "Authorization: token $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/$ORG_NAME/repos?sort=created&direction=desc&per_page=100")
# JST前日範囲に入る作成分だけ抽出
NEW_REPOS=$(echo "$REPOS" | jq -c --arg start "$START_UTC" --arg end "$END_UTC" '
[ .[] | select(.created_at >= $start and .created_at < $end) ]')
# 新規作成がなければ通知せず正常終了
if [ "$(echo "$NEW_REPOS" | jq 'length')" -eq 0 ]; then
echo "[INFO] No new repositories found in previous JST day window. Skip Slack notification."
exit 0
fi
# Slack本文を組み立て
MESSAGE=$(echo "$NEW_REPOS" | jq -r '
"<!here> 前日に作成された新規リポジトリ:\n"
+ (map("• <\(.html_url)|\(.name)> (" + (if .private then "private" else "public" end) + ")")
| join("\n"))')
# Slackへ送信
send_slack "$MESSAGE"
コードの説明・補足
on:
schedule:
- cron: '0 0 * * *' # UTC 00:00 = JST 09:00
workflow_dispatch:
schedule:で設定したcronの時間にGitHub Actionsが動きます。今回は日本時間9時に定期実行する設定です。
workflow_dispatch:を追加することで、任意のタイミングで手動実行できます。
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Check newly created repositories and notify Slack
env:
GH_TOKEN: ${{ secrets.ORGANIZATION_REPOSITORY_PAT }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
ORG_NAME: ${{ vars.ORG_NAME }}
run: |
set -Eeuo pipefail
send_slack() {
local text="$1"
curl -sSf -X POST -H 'Content-Type: application/json' \
--data "$(jq -n --arg text "$text" '{text: $text}')" \
"$SLACK_WEBHOOK_URL"
}
on_error() {
local exit_code=$?
trap - ERR
send_slack "<!here> GitHubリポジトリ通知ワークフローが失敗しました。" || true
exit "$exit_code"
}
trap on_error ERR
env:には、環境変数の設定で事前に登録したSecrets/Variablesをコード内に使えるよう定義しています。
set -Eeuo pipefailでは変数やコマンドの失敗、パイプライン途中の失敗を検知して処理を停止するよう設定しています。
send_slack()はSlackへのメッセージ送信処理を関数化し、通常通知とエラー通知の両方から呼び出せるようにしています。
trap on_error ERRにより、スクリプト内で何らかのコマンドが失敗した場合は自動的にon_errorが呼ばれ、失敗をSlackに通知してからワークフローを異常終了させます。
# JST前日範囲に入る作成分だけ抽出
NEW_REPOS=$(echo "$REPOS" | jq -c --arg start "$START_UTC" --arg end "$END_UTC" '
[ .[] | select(.created_at >= $start and .created_at < $end) ]')
# 新規作成がなければ通知せず正常終了
if [ "$(echo "$NEW_REPOS" | jq 'length')" -eq 0 ]; then
echo "[INFO] No new repositories found in previous JST day window. Skip Slack notification."
exit 0
fi
取得した全リポジトリの中から、先ほど計算したJST前日範囲のcreated_atを持つものだけをjqで絞り込んでいます。該当が0件なら通知するものがないため、Slackへは送らず正常終了させています。
# Slack本文を組み立て
MESSAGE=$(echo "$NEW_REPOS" | jq -r '
"<!here> 前日に作成された新規リポジトリ:\n"
+ (map("• <\(.html_url)|\(.name)> (" + (if .private then "private" else "public" end) + ")")
| join("\n"))')
# Slackへ送信
send_slack "$MESSAGE"
前日にリポジトリ作成されていた場合、抽出したリポジトリ名とPublic/Privateを箇条書きにしてSlack通知します。
まとめ
GitHub Actionsのschedule実行で組織におけるリポジトリ作成時のSlack通知を実装しました。初めてGitHub Actionsを使いましたが、バッチ処理がGitHub上で完結したので当初の想定よりも手軽に実装できました。
