Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Sansan iOS アプリにおけるリリース作業自動化の仕組みを作り直した話 ~ GitHub Actions 編 ~

こんにちは。 技術本部 Mobile Application Group で Sansan の iOS アプリを開発している山名です。
本記事は「Sansan iOS アプリにおけるリリース作業自動化の仕組みを作り直した話」の 3 本目で、最後の記事となります。

作り直すに至った経緯、Bolt を用いた Slack Bot 作成に関しては、以下の記事をご覧ください。

buildersbox.corp-sansan.com

buildersbox.corp-sansan.com

本記事では GitHub Actions を用いて、リリース作業自動化の仕組みを作り直した話について紹介します。

目次

これまでのおさらい

本題に入る前に、少しだけこれまでのおさらいをさせてもらいます。
(1, 2 作目に続いて読んでいただいている方は飛ばしてもらっても大丈夫です)

本記事で取り扱う範囲

まず、既存のリリース作業は大きく 2 つに分けられます。

  • 「deliver」コマンドによって起動するリリース用の作業
  • 「cleanup」コマンドによって起動するリリース掃除用の作業

ただこれらは一度に実装しきるには作業量が多いです。そのため、第 1 弾では比較的簡単な「cleanup」を、第 2 弾ではその知見を活かしてより難易度の高い「deliver」を実装する形で進めています。
本記事の執筆時点では第 1 弾のみ完了しているため、以下ではそちらの内容についてお話しします。

Bolt 製 Slack Bot の役割

Bolt 編」で解説した通り、「cleanup」において Bolt で作った Slack Bot はリリースブランチの情報を受け取ります。この情報を後述する GitHub Actions に渡し、実際のリリース作業が行われます。

f:id:yamannnu:20220302093651p:plain
Bolt 製 Slack Bot による対話的なリリース作業の様子

実現するリリース作業

さて、それでは本題です。
今回実現するリリース作業は作業は以下の通りです。

  1. リポジトリへのタグ打ちと Release の作成
  2. 今回分のリリース用 Issue の Close と、次回分の Open
  3. リリースブランチの削除
  4. TestRail プランのクローズ
  5. 結果を Slack へ投稿

それぞれについて解説します。

1. リポジトリへのタグ打ちと Release の作成

ここでいう Release は GitHub 上で作成するものを指します。弊チームではその本文にリリース用 Issue 内のリリースノートを使用します。リリース用 Issue はリリースに向けて様々な情報を貯めておく場で、その役割の 1 つが Release や社内告知の本文として使用するリリースノートを作ることです。このリリースノートは master (今風に言えば main)へ変更を取り込んだら更新する運用になっており、そのバージョンに含まれる変更内容や関連ドキュメントが網羅されているため、そのまま Release の本文に流用しています。

f:id:yamannnu:20220228082717p:plain
Sansan iOS におけるリリース用 Issue と Release

2. 今回分のリリース用 Issue の Close と、次回分の Open

前述したリリース用 Issue はバージョン毎に作成しています。リリース後は不要となるため Close し、次回リリース用のものを Open します。

3. リリースブランチの削除

Sansan iOS では release/3.3.3 のように、バージョン名でリリースビルド用のブランチを作成しています。これはリリース後に不要となるため、削除します。

4. TestRail プランのクローズ

背景編」でも触れましたが、Sansan iOS では QA と協業するために、TestRail というツールを用いてリグレッションテストの項目を管理しています。こちらもリリース後は不要となるため、クローズします。

5. 結果を Slack へ投稿

後片付けが終わったことの報告として、Slack へ結果を投稿します。内容はこれまで使用していた旧 Bot と同様です。

f:id:yamannnu:20220228082904p:plain
旧 Bot での「cleanup」時に Slack へ投稿される結果

実現方法

ここまでで具体的なリリース作業の内容について説明したので、ここからはその実現方法についてお話しします。

使用技術とその選定理由

GitHub Actions とは?

遅くなりましたが、本記事のメインテーマである GitHub Actions について解説します。
GitHub Actions はいわゆる CI / CD サービスで、GitHub との親和性が高いことが特徴です。具体的には以下のようなメリットがあります。

  • GitHub リポジトリへの導入、運用が楽
  • GitHub やリポジトリに関する情報を簡単に参照可能
  • ブランチの push や Pull Request の Open といったリポジトリのイベントを用いて、ワークフローを動作させられる

非常に簡単かつ便利なサービスなので、気になる方は Quickstart 等のドキュメントを読んでいただければと思います。

なぜ GitHub Actions なのか?

前述した GitHub との親和性の高さに加えて、メンバーの技術スタックに合っていることが大きいです。
Sansan iOS ではリリース作業以外にも様々なことが GitHub Actions によって自動化されており、多くのメンバーが開発・運用できる状態になっています。

  • Pull Request に対して作成者のアサイン、ラベル付与、仕様書のリンク投稿を行う(紹介記事
  • dependabot が作成した Pull Request に 2 Approve が付いたら自動マージする
  • base branch が master の場合、リリースノートの更新といった諸作業を忘れないよう、Pull Request へタスクリストを投稿する
  • Bug Fix Day に向けて毎月第三木曜日のマイルストーンを作成する(Bug Fix Day の紹介記事

GitHub Actions を用いた「cleanup」の構成

GitHub Actions によって実現した「cleanup」ですが、全体像は以下のようになっています。

f:id:yamannnu:20220301185213j:plain
GitHub Actions で作成した「cleanup」の全体像

以下の内容が新しく出てきたので解説します。

  • Workflow Dispatch
  • Incoming Webhook
  • 作成した 4 つのワークフロー

Workflow Dispatch とは?

Workflow Dispatch は、GitHub API 等から手動でワークフローをトリガーできる機能です。
トリガー時はワークフロー側で定義されている引数を渡すことが可能で、受け取った情報はワークフロー内で利用できます。今回の場合はリリースしたバージョンを引数に、Slack Bot から起点となる create_release を、create_release からも後続のワークフローを呼び出しています。
例として、Shell 上から create_release を呼び出すコードは以下のようになります。

jq -n --arg ref "feature/fix-release-process/add-create_release" --arg released_version "v3.3.3" '{
  "ref": $ref,
  "inputs": {
    "released_version": $released_version
  }
}' \
| curl -s \
    -X POST \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${GITHUB_PERSONAL_API_TOKEN}" \
    -d @- \
    "https://api.github.com/repos/Yonyon/iOS/actions/workflows/create_release.yml/dispatches"

Incoming Webhook とは?

Incoming Webhookは、外部サービスから Slack へメッセージを投稿する手段の 1 つです。
一応、これ以外にもいくつか方法はあります。ただ今回の場合はユーザーとのインタラクションがなく、一方的に送りつけるだけで良かったため、その制約の元であれば一番簡便な手段であるこちらを採用しました。
例として、Shell 上から Hello, World! というメッセージを Slack へポストするコードは以下のようになります。

curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' ${INCOMING_WEBHOOK_URL}

作成した 4 つのワークフロー

1 つ前の章では 5 つのリリース作業があると説明しました。

  1. リポジトリへのタグ打ちと Release の作成
  2. 今回分のリリース用 Issue の Close と、次回分の Open
  3. リリースブランチの削除
  4. TestRail プランのクローズ
  5. 結果を Slack へ投稿

上記リリース作業を、それぞれのワークフローが以下のように担っています。

ワークフロー リリース作業
create_release 1. リポジトリへのタグ打ちと Release の作成 + 5. 結果を Slack へ投稿
prepare_for_next_release 2. 今回分のリリース用 Issue の Close と、次回分の Open
delete_release_branch 3. リリースブランチの削除
close_testrail_plan 4. TestRail プランのクローズ

create_release に関しては GitHub Actions 側のまとめ役を担う関係上、後続ワークフローの呼び出しも担っています。また全て並列実行可能なタスクのため、特に順序は設けず同時に呼び出しています。

尺の都合上具体的なコードまでは解説できませんが、

  • GitHub に関する操作は GitHub API
  • Slack へポストするメッセージの生成等のテキスト処理は sed や awk といった Shell Script の機能

を使用して実現しています。

GitHub Actions の実装 Tips

具体的なコードを解説できなかったお詫びとして、ここからは GitHub Actions を実装するにあたって知っておくと便利な Tips を紹介します。

デフォルトブランチ以外で Workflow Dispatch が 404 になる(2022/1 時点)

私含むチームメンバー数人が地味に悩まされたやつです。 どういうことかというと、master や main 等のデフォルトブランチ以外では、一度そのワークフローが呼び出されていないと Workflow Dispatch しても 404 になってしまいます(参考記事)。 普通何かしらの機能を開発する際はデフォルトブランチから切って作業すると思いますので、これは結構厳しい & 謎な仕様でした...。ただ幸いなことに on: push 等の適当な条件で一度でも呼び出してしまえば、以降はデフォルトブランチ以外でも Workflow Dispatch できるようになるため、そこまで大きな問題ではありませんでした。

※ この記事が世に出ている or 読んでもらっている時点ではもしかしたら解消されているかもしれませんので、一度試していただけますと幸いです。

context の活用

詳しくは Contexts のページを見ていただきたいのですが、context を活用することによって様々なメリットを享受できます。

  • テキストベタ打ちではないため、変更に強いコードになる
  • リポジトリやワークフローに関する情報に簡単にアクセスでき、実装時間の短縮になる
  • Personal Access Token のようなログに出力したくない情報を自動で隠すことができ、安全性が向上する

上記メリットについて、Workflow Dispatch の説明で用いたコードを使って解説します。
${{ <context> }} で表現されている部分がそれにあたります)

# Before
jq -n --arg ref "feature/fix-release-process/add-create_release" --arg released_version "v3.3.3" '{
  "ref": $ref,
  "inputs": {
    "released_version": $released_version
  }
}' \
| curl -s \
    -X POST \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${GITHUB_PERSONAL_API_TOKEN}" \
    -d @- \
    "https://api.github.com/repos/Yonyon/iOS/actions/workflows/create_release.yml/dispatches"


# After
jq -n --arg ref "${{ github.ref_name }}" --arg released_version "v3.3.3" '{
  "ref": $ref,
  "inputs": {
    "released_version": $released_version
  }
}' \
| curl -s \
    -X POST \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${{ secrets.PERSONAL_API_TOKEN }}" \
    -d @- \
    "${{ github.api_url }}/repos/${{ github.repository }}/actions/workflows/create_release.yml/dispatches"

github に関しては、GitHub API を呼び出す際に必要な諸々の情報を提供してくれています。 例えば github.api_urlhttps://api.github.com という文字列を返します。 これによって都度打つ労力が減りますし、タイポする心配もなくなるので非常にありがたいです。

また secrets に関しては、Settings > Security > Secrets > Actions に登録した情報が使用可能です。 その名の通り秘密にしておきたい情報を扱う際に使用する context で、ログに出力される際は自動でマスクしてくれます。 ただし、echo 等で意図的に出力すると隠れないらしいので、その辺りはご注意ください。

f:id:yamannnu:20220301192802p:plain
env に登録した secrets がログでマスクされている様子

まだまだ紹介しきれていないですが、どれも便利なのでぜひ一度目を通していただければと思います。

(超絶ニッチなメリットですが、こういう記事にそのままコピペできるのも嬉しいですね...いつもは色々配慮して書き換える必要があるので)

slack-github-action の導入

Slack へのメッセージ投稿ですが、curl 等で投稿しようとすると微妙に読みづらく、もっと分かりやすく書ける方法がないかな...とお悩みの方に朗報です。
slack-github-action の出番です。
Slack 公式が提供してくれているので安定しており、なおかつ使い勝手も良いです。
使い方もしっかり解説してくれています。

qiita.com

以下は Incoming Webhook を用いて、作成した Release の URL を Slack へポストする際のコードです。

# Before
- name: Post Release URL to Slack
  run: |
    curl -s \
      -X POST \
      -H 'Content-type: application/json' \
      ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} \
      --data '{"text":"Release completed ! :tea:\n${{ needs.create_release.outputs.release_url }}"}' 


# After
- name: Post Release URL to Slack
  uses: slackapi/slack-github-action@v1.17.0
  env:
    SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
  with: 
    payload: |
      {
        "text": "Release completed ! :tea:\n${{ needs.create_release.outputs.release_url }}"
      }

正直上記程度の簡単な内容であれば大差ないのですが、もう少し複雑なメッセージを送ったりする時はより効果を発揮しますので、ぜひご検討ください。
(各種設定とメッセージが明確に分かれていて読みやすい感じが好きなので、私は余程の理由がない限りこれを使用しています)

shellcheck, actionlint の導入

shellcheck は Shell Script の、actionlint は GitHub Actions の静的解析ツールです。
(actionlint に関しては作者様のブログが非常に分かりやすいので、よろしければご覧ください)

rhysd.hatenablog.com

文法の誤りや望ましくない記述を教えてくれるため、私のような Shell Script にも GitHub Actions にも明るくない人間にとっては非常に助かるツールです。 またチーム内である程度記法を揃えることができて可読性が上がるため、運用コストの削減にも繋がります。

今後の展望

現状ひとまず第 1 弾はリリースできており、実際にチーム内で使われています。しかし、開発終盤や実際に使ってみて判明した改善点には対応しきれていません。
この章では、そんな伸びしろ達についてお話ししようと思います。

GitHub CLI の活用によるコードの短縮

GitHub CLI はその名の通り GitHub の諸々に CLI から簡単にアクセスするためのツールです。 curl 等で操作するよりも記述量を大幅に短くできるため、可読性の向上が見込めます。 しかも GitHub Actions の環境に pre-install されているので、簡単に使用可能です。

ただ私の調査不足により、こちらの存在が明るみに出たのは開発が終わりかけの段階でした...。 なおかつ工数に余裕があるわけでもなかったため、第 1 弾の時点では愚直に curl しています。 Release の作成を例に出すと↓くらいの量のコードが減らせるので、機会を見つけて置き換えていきたいです。

# Before
jq -n --arg version "v3.3.3" --arg body "Release Note" '{
  "name": $version,
  "body": $body,
  "tag_name": $version,
  "target_commitish": "master"
}' \
| curl -s \
    -X POST \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${GITHUB_PERSONAL_API_TOKEN}" \
    -d @- \
    "https://api.github.com/repos/Yonyon/iOS/releases" \


# After
gh release create "v3.3.3" -t "v3.3.3" -n "Release Note"

命名の見直しによる一覧性の向上

f:id:yamannnu:20220228083814p:plain
現状のワークフロー一覧

こちらは実際に運用に乗って少しして判明した問題です。 最近はリリース作業に限らずチーム内で GitHub Actions が積極的に活用されており、多くのワークフローが誕生しています。 ただその弊害として、パッと見でどのワークフローがどこで使われているのか分かりづらいという声が上がりました。 まだ実害が出るレベルではないので一旦保留になりましたが、リポジトリ内の GitHub Actions についてきちんと解説したドキュメントを作成する、prefix で区別できるようにするなどで対策していきたいです。

諸々の機能追加

こちらも実際に運用に乗って判明した内容です。改めてリリース作業を見つめ直したことや、責務の分割により機能拡張が現実的になったことで、様々な改善案が出てきました。

  • ワークフロー実行中に各種処理の成否を都度進捗をポストするといった、待ち時間中のフォロー
  • 「cleanup」後に人の目で確認すべきタスクがあるので、完了次第それをポストする
  • 選択したリリースブランチや、選択 / キャンセルした人をポストする

Bolt の導入に加え、ほとんどの処理を GitHub Actions 側に移したことにより圧倒的にデバッグしやすくなったので、これから少しずつ改善していきたいと思います。

連載を通してのまとめ

リリース作業自動化シリーズ最後の記事なので、軽く全体を振り返りつつ、所感を述べさせていただきます。

全体の振り返り

まず、置き換え前は以下の課題がありました。

  1. 他の iOS チームメンバーの技術スタック的に Go 言語でのメンテナンスに学習コストがかかる
  2. 開発環境が整備されておらず、機能追加や修正の動作確認がやりづらい
  3. 依存関係のないリリース作業のステップも全て Slack bot 内に実装されていたので、特定のステップを修正するのにも Slack bot 自体に手を入れる必要があり、修正スコープが大きくなりがちだった

これらの課題に対し、以下の方針で解決を図りました。

  1. 前述の課題を解決する
    1. iOS チームのメンバーがメンテナンスできる技術の選定
    2. 開発環境の整備
    3. 自動化の仕組みにおける責務の分割
  2. 既存の運用方法は大きく変えない
    1. 作り直しによって運用に混乱が生じないように ChatOps で行なっている Slack bot との対話インターフェースは大きく変化させない

それぞれの達成状況は以下の通りです。

達成状況 方針 解決策
1.1 Slack Bot はドキュメントの高い充実度と一部のメンバーに知見があることから Bolt for JavaScript を採用した。また実際の処理に関しては多くのメンバーに知見がある GitHub Actions を採用した
1.2 ワンコマンドでローカルに開発用サーバを立てることが可能な netlify-cli を採用した
1.3 まず責務に関しては、開発者とのやりとりは Bolt 、実際の処理は GitHub Actions という棲み分けをした。また GitHub Actions 自体もワークフローを責務ごとに細かく分割することで、より単体でデバッグしやすくした
2.1 従来のインターフェースを踏襲した

所感

実際に運用してみての感想としては、月並みですが非常に満足しています。
これまでは

  • 開発環境の構築に時間がかかる
  • 不慣れな Go なので理解〜実装に時間がかかる
  • 部分的にデバッグしたくても全体が実行されてしまうので、色々と生成されたものを片付ける必要があり時間がかかる

と修正に中々の時間がかかってしまうため、気軽には修正できない状態でした。

しかし、今回の置き換えによりそれがかなり改善されました。 実際リリースしてからしばらくした後に想定していなかったバグが見つかったのですが、原因の特定と修正、動作確認まで 1 時間足らずで完了できました。 また master へマージする前の Pull Request でも何点も改善提案が入ったのですが、そちらの修正〜動作確認も 1 時間足らずで完了できました。

これは何度か触ったことがある身としては驚異的な手軽さで、これからも積極的に改善していこうと思えるものになっています。
置き換え中は色々と辛いこともありましたが、改めてやって良かったと心から思える内容でした。

おわりに

本記事では、Sansan iOS チームで運用しているリリース作業を GitHub Actions を用いて自動化した経緯についてお話ししました。
これから GitHub Actions を使う方や、リリース作業を自動化される方の参考に少しでもなれば幸いです。

また冒頭で紹介した第 2 弾の「deliver」ですが、もうそろそろ完成しそうです。
「cleanup」を土台とした「deliver」では今後の展望でお話しした一部内容の実装、その他にも Composite Action の活用など様々な改善がなされています。 加えてリリース作業が完全に置き換わってから分かることもあるので、もしかしたら幻の 4 本目に続くかもしれません...が現状は未定となっておりますので、もしよろしければ気長にお待ちいただければと思います。

最後まで読んでいただき、ありがとうございました。

© Sansan, Inc.