Github Actionsに色々任せよう -Milestoneの期限をSlackに通知する-

thumbnail

 

サントリーウエルネス DX推進部エンジニアリングGの青木です。

日々の業務でGithubを利用しているのですが、Github Actionsを使えば面倒なことを任せられることがわかり試行錯誤しながら導入しています。
今回はその第一弾として、Milestoneの期限通知をGithub Actionsで行う方法について紹介します。

Github Milestoneとは

Github Milestoneとは、対象リポジトリのバージョンに紐づく変更管理を容易にする機能です。
Milestoneに紐づけたIssueやPull Requestの進捗をパーセンテージ表示したり、リリース時になんの変更がそのMilestoneに紐づいているかを管理したりできます。 私のチームではセマンティック・バージョニングに基づいてMilestoneを作成しており、そのバージョンに含まれる変更管理をすべてMilestoneで行っています。

Milestoneは変更管理に強く、期限管理に弱い

Milestoneは変更管理には強いのですが、それに特化している機能であるとも言えると思います。期限に関してはカレンダーとして出るわけでもないですし、Milestoneにちょっと記載されているレベルです。これではぱっと見たときにいつがリリース日なのかがわかりません。 そのため、他のツールでスケジュール管理しておく必要が出てくるのですが、せめて期限通知くらいはできたら楽なのにな…と思いました。

そうだ、Github Actionsに任せよう

この期限通知はGithub Actionsを使って行えます。Milestoneの期限を取得する必要がありますが、同じGithubなので構築が楽です。
特にGithub Actionsごとに発行されるGITHUB_TOKENがあるのでわざわざPATを発行しなくていい点も気軽だと思います。

構成

全体構成は下記の通りです。
週次でMilestoneの期限を確認しにいき、1ヶ月以内であれば事前に用意しておいたSlackアプリにPOSTするWorkflowを用意するだけです。非常に簡単な作りですが、これだけでMilestoneの期限通知を自動化できます。

Workflow

作成したWorkflowはこんな感じです。
毎週水曜日の朝9時にSlackチャンネルに通知させるようにします。
envに各リポジトリの情報をセットするように書けば横展開も楽なのでオススメです。
※Github Acitonsで立ち上がるコンテナはUTCなので注意。

name: Milestone Notifier

on:
  schedule:
    - cron: '0 0 * * 3' # UTCで毎週水曜日の0時(日本時間で毎週水曜日の9時)
  workflow_dispatch:

jobs:
  notify-milestones:
    runs-on: ubuntu-latest

    steps:
    - name: Check out code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install requests        

    - name: Check Milestones and Notify Slack
      env:
        REPOSITORY: ${Organization}/${Repository}
        SLACK_WEBHOOK_URL: ${SlackアプリのURL}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        APP_NAME: ${Applicaton名}
      run: |
        python .github/scripts/check_milestones.py        

スクリプト

Workflow内でスクリプトを書く方法もあるのですが、可読性が低くなるので別ディレクトリで管理しています。

Workflowで定義しておいた環境変数を受け取って実行するように作成しているのでスクリプト自体はどのリポジトリでも使えます。

やっていることは、下記の通りです。

  1. Milestoneの情報をGET
  2. GETしたMilestoneの期限をチェックして取得日から30日以内であればメッセージに必要な変数をセット(取得したMilestone分繰り返す)
  3. 通知が必要なMilestoneがあれば変数を利用してメッセージラインを作成してSlackにPOST、されていなければPOSTせずに終了
import requests
from datetime import datetime, timedelta
import os

def get_milestones(repo, token):
    url = f"https://api.github.com/repos/{repo}/milestones?state=open"
    headers = {"Authorization": f"token {token}"}
    response = requests.get(url, headers=headers)
    return response.json()

def notify_slack(webhook_url, message):
    payload = {"text": message}
    requests.post(webhook_url, json=payload)

def main():
    repo = os.environ["REPOSITORY"]
    token = os.environ["GITHUB_TOKEN"]
    webhook_url = os.environ["SLACK_WEBHOOK_URL"]
    app_name = os.environ["APP_NAME"]

    milestones = get_milestones(repo, token)
    today = datetime.today()
    upcoming_milestones = []

    for milestone in milestones:
        if milestone.get("due_on"):
            due_date = datetime.strptime(milestone["due_on"], "%Y-%m-%dT%H:%M:%SZ")
            if today <= due_date <= today + timedelta(days=30):
                milestone_url = f"https://github.com/{repo}/milestone/{milestone['number']}"
                upcoming_milestones.append({
                    "title": milestone["title"],
                    "due_on": milestone["due_on"][:10],
                    "url": milestone_url
                })

    if upcoming_milestones:
        message_lines = ["<!subteam^${チームメンションID}>\n\n次のMilestoneの期限が30日以内です。\nご認識お願いします。\n"]
        for milestone in upcoming_milestones:
            message_lines.append(f"App:{app_name}\nMilestone:{milestone['title']}\nURL:{milestone['url']}\n期限:{milestone['due_on']}\n---\n")

        message = "\n".join(message_lines) + "\n"
        notify_slack(webhook_url, message)

if __name__ == "__main__":
    main()

POST先のSlackアプリを作成する

SlackにはSlack APIが用意されており、開発者が柔軟性を持って比較的簡単にSlackのアプリを作成できます。

ここではGithub ActionsからPOSTされたらWebhookでSlackのチャンネルにそのままPOSTするアプリを作成します。
詳しい作成手順は公式サイトをご参照ください。

  • Create New AppからScratchで新規アプリを作成する
  • 作成できたらIncoming Webhookに移動し、Activate Incoming WebhhoksをOnにする。
  • 同ページ下部のWebhook URLs for Your WorkspaceにからAdd New Webhook to Workspaseを押下し、AppとWorkspaceの連携を許可する
  • 許可できたら連携するSlackチャンネルを設定する。
  • WebhookのURLをコピーして先ほど作成したGithub ActionsのWorkflowのSLACK_WEBHOOK_URLに設定する。
    • 不特定多数の人がリポジトリにアクセスできる環境であれば、Github Secretsを使ってWebhookURLを取得してきたほうが良い。

実行結果

これで準備が整いました。
実行結果を見てみましょう。

いい感じですね!
Milestoneが複数あった場合でも1メッセージでPOSTできていることが確認できました。
※複数件Milestoneがあった場合にその都度メッセージを送ると通知が飛びすぎて迷惑になるので1メッセージだけ送信したほうが良いです。

おわりに

Github Actionsを活用すれば面倒な確認作業を任せられることがわかりました。
スクリプトも呼び出せるのでWorkflow + lambdaみたいな感覚で扱えそうです。
今回はSlackアプリにPOSTしてWebhookさせるところまでで終わらせましたが、スケジュール管理アプリがあればそこに連携させて自動更新とかも全然できるなと思いました。
以上、参考になれば幸いです!