🚀

GitHub Enterprise 使わなくてもデプロイを承認制にできるよ??

2024/07/10に公開

こんにちは!営業製作所でエンジニアをやっている秋山です 🍔

本記事では、GitHub Enterprise を使用せずに、デプロイを承認制にする方法を解説します。

対象読者:

  • GitHub Enterprise を導入していない開発者

背景と目的

弊社プロダクトはリリースして間もないということもあり運用がまだ固まっておらず、誰でも本番環境へデプロイができる状態でした。
これは今後プロダクトを運用していくにあたって好ましい状態ではありません。
ただ、弊社の GitHub プランは Enterprise ではないためデプロイを承認制にすることができませんでした。

金銭的にも安く済ませるに越したことはないため、GitHub Enterprise に入らずにデプロイを承認制する方法を考える必要がありました。

どのように実現するか?

🍳 用意するもの

GitHub Actions
・開発環境へのデプロイ
・ステージング環境へのデプロイ
・本番環境へのデプロイ

Slack チャンネル
・開発環境
・ステージング環境
・本番環境

🚀 フローの概要

  1. GHA : 開発環境へデプロイ
  2. Slack : 開発環境チャンネルにデプロイ完了通知
    開発環境チャンネルに QA 完了ボタンを表示
  3. QA 完了 ✨
  4. Slack : 開発環境チャンネルの QA 完了ボタンを押下
  5. Slack : ステージング/本番チャンネルに[デプロイ]ボタンを表示
  6. Slack : [デプロイ]ボタンを押下
  7. GHA : ステージング/本番へデプロイ

利用する機能

では次に実装にまつわる話に移ります。
主に使用した機能やツールはこちらになります。

  • GitHub Actions
  • Slack Workflow
  • Slack Custom Function

なぜ Slack Custom Function を使うのか?

標準の Slack Workflow では実現できないことが 2 点ありました。

  • 1 つのアクションから同時に 2 チャンネルにメッセージを送れない
    • 開発環境で QA 確認完了後「ステージング環境」「本番環境」の 2 チャンネルに送りたい
  • 外部 API を呼び出すことができない
    • Slack から GitHub Actions を実行したい

これを解決するためにSlack Custom Functionを使用する必要がありました。

Slack Custom Function ってなに?

先述したフローに当てはめると5, 6 番目にあたるステップがカスタムファンクションを使わないと実現できない機能です。

  1. GHA : 開発環境へデプロイ
  2. Slack : 開発環境チャンネルにデプロイ完了通知
    開発環境チャンネルに QA 完了ボタンを表示
  3. QA 完了 ✨
  4. Slack : 開発環境チャンネルの QA 完了ボタンを押下
  5. Slack : ステージング/本番チャンネルに[デプロイ]ボタンを表示
  6. Slack : [デプロイ]ボタンを押下
  7. GHA : ステージング/本番へデプロイ

Slack Custom Function の詳細を知りたい方はこちらから
↓↓↓↓↓↓↓
https://api.slack.com/tutorials/tracks/wfb-function

実装例

次に実装の話に移ります。
承認制デプロイを実現するためには大きく分けて 3 つ実装する必要があります。

  • GitHub Actions
    • 開発環境デプロイ後 Slack に通知する部分の実装する
    • SlackからAPI経由で実行するワークフロー
  • Slack Workflow
    • 開発環境デプロイ後の通知を受けて開発環境チャンネルにメッセージを送信するように設定する
    • 開発環境チャンネルでボタンが押されたら後述する Slack Custom Function を呼び出すように設定する
  • Slack Custom Function
    • 開発環境チャンネルで「QA 確認完了」ボタンを押されるとステージングチャンネル, 本番チャンネルに「デプロイボタン」付きメッセージが飛ぶように実装する
    • デプロイボタンを押すと GitHub Actions が実行するように実装する

それぞれの詳細は次の項目で紹介します。

GitHub Actions 作成

  • 開発環境デプロイ後 Slack に通知する部分の実装する
    後述で作成する Slack Workflow の URL に POST リクエストを行います。
    この際に後続処理で必要になる変数を渡すことも可能です。
    👇 実装例(一部抜粋)

    send_slack_notification:
      runs-on: ubuntu-latest
      steps:
        - name: Send API Request
          run: |
            curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
              -H "Content-Type: application/json" \
              -H "Authorization: Bearer ${{ secrets.SLACK_WEBHOOK_TOKEN }}" \
              -d slackに渡したいパラメータがあればここに記述
    
  • Slack から API 経由で実行されるようにする部分の実装する
    repository_dispatch を使用することで API 経由でWorkflowを実行できるようになります。
    types で受け取ったパラメータを元に実行するワークフローを指定することも可能です。
    👇 実装例(一部抜粋)

    on:
      repository_dispatch:
        types: [production]
    

Slack Workflow 実装

なるべく実装コストを減らすために Slack Workflow で実現できる部分は Slack Workflow を使用します。

作成する workflow はいたってシンプルで以下の機能を持った workflow を作成します。

  1. 開発環境デプロイ後の通知を受け取れるように Webhook から発火されるようにする
  2. 開発環境チャンネルに「QA 確認完了」ボタンを通知する
  3. QA 確認完了」ボタンを押下したらCustom Functionを呼び出す

※もちろん最初から最後まで Custom Function で作成することも可能です。

Slack Workflow の詳細はこちらから参照できます。
↓↓↓↓↓↓↓
https://slack.com/intl/ja-jp/help/articles/16583775096083-自動化---Slack-ワークフローとは

Slack Custom Function 実装

事前準備

slack cli を使えるようにします。

curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash

slack コマンドが使用可能になっているか確認します。

slack help

slack にログインします。

slack login

Slack Custom Function の使い方

下記コマンドでアプリを新規作成します。

slack create my-app

テンプレートをもとに作成すると下記のようなファイルが自動生成されます。

.
├── LICENSE
├── README.md
├── assets
│   └── default_new_app_icon.png
├── deno.jsonc
├── functions
│   └── post_issue_message.ts
├── import_map.json
├── manifest.ts
├── slack.json
├── triggers
│   └── submit_issue.ts
└── workflows
    └── submit_issue.ts

この workflow をローカルで動作確認する場合は下記コマンドを使用します。
これで Slack の画面から試すことが可能になります。

slack run

ローカルで動作確認しつつテンプレートを編集していくと実装のイメージがつきやすそうですね。


Slack Custom Function 実装

下記機能を実装します。

  • 開発環境用チャンネルで「QA 確認完了」ボタンを押下後、ステージング用チャンネルと本番環境用チャンネルに「デプロイ」ボタンを通知
  • 「デプロイ」ボタンが押されたら GItHub Actions を実行する

実際に作成したカスタムファンクションはこちらのリポジトリから参照できます。
↓↓↓↓↓↓↓
https://github.com/eigyo-mfg/slack-deployment-approval-flow

上記リポジトリから一部抜粋して説明していきます。

Slack Workflow から呼び出すメソッドを定義します。
受け取りたい変数を定義します。

export const PostDeployMessage = DefineFunction({
  callback_id: "post_deploy_message",
  title: "デプロイボタン表示をするメッセージ",
  description:
    "各環境向けにデプロイボタンを表示するためのメッセージを投稿します",
  source_file: "functions/post_deploy_message.ts",
  input_parameters: {
    properties: {
      commitHash: {
        description: "デプロイするコミットのハッシュ",
        type: Schema.types.string,
      },
      githubRepositoryOwner: {
        description: "デプロイ対象リポジトリのオーナー",
        type: Schema.types.string,
      },
      githubRepository: {
        description: "デプロイ対象リポジトリ",
        type: Schema.types.string,
      },
      sendToSlackChannelIdStaging: {
        description: "ステージング:Slackに通知するチャンネルID",
        type: Schema.types.string,
      },
      sendToSlackChannelIdProduction: {
        description: "本番:Slackに通知するチャンネルID",
        type: Schema.types.string,
      },
    },
    required: [
      "commitHash",
      "githubRepositoryOwner",
      "githubRepository",
      "sendToSlackChannelIdStaging",
      "sendToSlackChannelIdProduction",
    ],
  },
});

次に開発環境用チャンネルで「QA 確認完了」ボタンが押された後にステージング環境用のチャンネル、本番環境用のチャンネルにメッセージを送る処理を実装します。

それぞれのチャンネルには下記のようにメッセージを送信しています。

  • コミットハッシュ
  • デプロイボタン
  • キャンセルボタン
export default SlackFunction(PostDeployMessage, async ({ inputs, client }) => {
  await postMessage(client, {
    commitHash: inputs.commitHash,
    channel: inputs.sendToSlackChannelIdStaging,
    repository: inputs.githubRepository,
  });
  await postMessage(
    client,
    {
      commitHash: inputs.commitHash,
      channel: inputs.sendToSlackChannelIdProduction,
      repository: inputs.githubRepository,
    },
    true
  );
  return { completed: false };
});

const postMessage = async (
  client: SlackAPIClient,
  postParams: {
    commitHash: string;
    channel: string;
    repository: string;
  },
  prod: boolean = false
) => {
  await client.chat.postMessage({
    channel: postParams.channel,
    blocks: [
      {
        type: "section",
        text: {
          type: "plain_text",
          text: `コミット:${postParams.commitHash}`,
        },
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: `🚀デプロイ(${prod ? "本番" : "ステージング"}`,
            },
            action_id: `${postParams.channel}-deploy`,
          },
        ],
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: `🚧キャンセル`,
            },
            action_id: `${postParams.channel}-cancel`,
          },
        ],
      },
    ],
  });
};

これだけでは「デプロイ」ボタンを押しても何も実行されません・
なので次は「デプロイ」ボタンが押下された時に実行される関数を実装します。
ここでは GitHub の API を使用して GitHub Actions を実行させる必要があるので下記のような実装になります。

const dispatchGithubActions = async (
  params: DispatchGithubActionsParams,
  env: Env
) => {
  return await fetch(
    `https://api.github.com/repos/${params.owner}/${params.repository}/dispatches`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${env.GITHUB_TOKEN}`,
        Accept: "application/vnd.github.v3+json",
      },
      body: JSON.stringify({
        event_type: `${params.environment}`, // "staging | production"
        client_payload: {
          // ここに渡したい引数を設定
        },
      }),
    }
  )
    .then((response) => response.ok)
    .catch((error) => {
      console.error(error);
      throw new Error("Failed to dispatch Github Actions");
    });
};

ポイントはbodyevent_typeGitHub Actions 側のrepository_dispatchtypes で指定している名称を合わせる必要がある点です。
運用する時は「staging」「production」の環境名を指定しています。

Slack Workflow から Slack Custom Function を呼び出す

実装が一通り終わりローカルでの動作確認が完了したらデプロイして実装完了となります。

slack deploy

まとめ

今回は Slack を活用することでGitHub Enterprise プランを使わずにデプロイを承認制にする方法を紹介しました。
Slack Custom Function を初めて作ってみましたが、自分でコードを書く分自由度が高く色んなことに使えそうと思いました。

GitHubで編集を提案
営業製作所株式会社

Discussion