
最終更新日: 2025年03月11日(火)
- 1. ご挨拶
- 2. 「スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう!」シリーズ
- 3. スクリプトの要件と仕様と登場クラス群と GitHub Actions
- 4. スクリプト実装
- 5. GitHub Actions 実装
- 6. 実行結果・差分
- 7. 「スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう!」本編の最後に
- 8. 次回予告
- 9. バックナンバー
1. ご挨拶
こんにちは。
前回の「【本編】スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう! Vol.2 -スクリプト実装編-」の続編を投稿します、@hayat01sh1da です。
ソフトウェアエンジニアとして、以下のサービスの進路・学事向け機能(e.g. アンケート配信機能、進路希望調査配信機能、面談ダッシュボード)を開発・運用・保守しています(for SCHOOL ブランドは先生の管理画面である「先生アプリ」の開発に従事)。

前回の記事の中で、古の時代から現在まで多くのチームやメンバーが相乗りで Snippets 置き場として活用している GitHub Wiki を、Ownership 不在かつ要・不要が不明な玉石混交な状態から Ownership 単位で Wiki が整理された状態にするスクリプトを実装したお話をしました。
しかし、その段階では残課題があり、その中で私が強調したのは「スクリプトを組んだだけでは完全な自動化ではない、その実行を含めてシステムに任せてこそ属人性が排除出来る」ということでした。
さて、今回は前回の記事で言及した要件内容を踏まえ、どのような仕様で GitHub Actions の Workflows を組んだのかを共有させて頂きます。
スクリプトの追加実装が必要でしたので、併せて説明させて頂きます。
スクリプトは非常にシンプルな実装ですので、Ruby/Python歴の浅い方でも理解出来る内容かと思います。
Workflows は YAML 記法と、GitHub Actions の知識が少々必要となりますが、出来るだけ噛み砕いて説明します。
【余談】
Ruby と Python の2つのスクリプト言語で同じアルゴリズムを実装しているのには、一応理由があります。
単純に「普段 Python を書く機会が乏しいので錆びつかせたくない」という個人的な思惑と「言語の制約でどのような実装差分が出るかや書き味の違いを知り続けたい」というプログラマーとしての思惑があります。
「PyCall 使えばええやん!」というツッコミがありそうなので予防線張っておきました(今回はそもそも Python Library の API をコールしていないので不要)。
個人的には、最初の言語ということと業務で使う機会が多いこともあり、Ruby の方が好きです。
あと、Python は完全独学でレビュー受けたことがないので、どなたかツッコミください 🙏
また、GitHub Actions の Workflows のデバッグの良い方法をご存知の方は教えてください 🙇🏻♂️
2. 「スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう!」シリーズ
全4部作の構成でお届けしています!
blog.studysapuri.jp blog.studysapuri.jp blog.studysapuri.jp blog.studysapuri.jp
3. スクリプトの要件と仕様と登場クラス群と GitHub Actions
3-1. 要件
- GitHub Wiki の Home と Sidebar を最新化する GitHub Actions の Workflows が平日の 12:00 と 22:00 の2回走り、その実行結果が Slack の指定チャンネルに通知されること。
- 月曜日の 09:00 に「Ownerチームが不明だが必要なページ群」「Ownerチーム・要or不要が不明なページ群」「Owner記名なし」のそれぞれの件数が Slack の指定チャンネルに通知されること。
3-2. 仕様
「【本編】スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう! Vol.2 -スクリプト実装編- > 3-2. 仕様」からの増分のみ掲載しています。
- Ownership なし Wiki 群(以下、マッピング②)を生成します。
- 使い回す際に再計算を行うことで CPU に負荷をかけないようにメモリ上にキャッシュしておきます
- Ownership なし Wiki 群の分類ごとの件数をテキストファイルに書き込みます。
- 平日の 12:00 と 22:00 に Update Wiki List on Home and Sidebar を実行し、GitHub Wiki の Home と Sidebar を Ownership 単位で Wiki を一覧化し、その実行結果を指定の Slack チャンネルに通知します。
- 月曜日の 09:00 に Notify Unowned Wiki List を実行し、Ownership なし Wiki 群の分類ごとの件数を指定の Slack チャンネルに通知します。
3-3. 登場クラス群
3-3-1. Ruby
| クラス名 | 役割 | 説明 |
|---|---|---|
| Application | 基底クラス | Home と Sidebar が共通で扱うデータを保持します。#run は継承先で実装されることが期待されているので、直接呼び出すと NotImplementedError を Raise します。対応するテストクラスは ApplicationTest です。 |
| UnknownWikiListExporter | Ownership なし Wiki 群ごとの件数書込みクラス | h2(Markdown 記法の ##)で名前空間を作り、その単位で Wiki を一覧化します。対応するテストクラスは UnknownWikiListExporterTest です。 |
| exec/export_unowned_wiki_list.rb | 実行ファイル | 実行時に README > 2. Execution > 2-1. Update Wiki List on Home and Sidebar 記載の標準出力を行います。 |
3-3-2. Python
| クラス名 | 役割 | 説明 |
|---|---|---|
| Application | 基底クラス | Home と Sidebar が共通で扱うデータを保持します。#run は継承先で実装されることが期待されているので、直接呼び出すと NotImplementedError を Raise します。対応するテストクラスは TestApplication です。 |
| UnknownWikiListExporter | Ownership なし Wiki 群ごとの件数書込みクラス | h2(Markdown 記法の ##)で名前空間を作り、その単位で Wiki を一覧化します。対応するテストクラスは TestHome です。 |
| exec/export_unowned_wiki_list.py | 実行ファイル | 実行時に README > 2. Execution > 2-1. Update Wiki List on Home and Sidebar 記載の標準出力を行います。 |
3-3-3. GitHub Actions
| クラス名 | 役割 | 説明 |
|---|---|---|
| .github/workflows/update-wiki-list-on-home-and-sidebar.yml | Ownership ごとの GitHub Wiki の Home と Sidebar 上の Wiki 一覧化ジョブ | 平日の 12:00 と 22:00 に定期実行される CronJob です。 |
| .github/workflows/notify-unowned-wikis.yml | Ownership なし Wiki 群の分類ごとの件数通知ジョブ | 毎週月曜日の 09:00 に定期実行される CronJob です。 |
4. スクリプト実装
ここではそれぞれのクラスの実装を、テストファイルで期待されている仕様から逆算して説明します。
ソースコードはハイパーリンクの遷移先をご参照下さい。
説明にあたり、手元に Clone した Wiki は以下のディレクトリ構造を持つものとします。
$ tree . ├── Home.md ├── Ownerチーム・要or不要が不明なページ.md ├── Owner記名ありページ.md ├── Owner記名なしページ1.md ├── Owner記名なしページ2.md ├── Ownerチームが不明だが必要なページ.md ├── _Sidebar.md ├── export_unowned_wiki_list.sh ├── github-wiki-organisers │ ├── LICENCE.txt │ ├── README.md │ ├── python │ │ ├── README.md │ │ ├── exec │ │ │ ├── export_unowned_wiki_list.py │ │ │ └── update_wiki_list_on_home_and_sidebar.py │ │ ├── src │ │ │ ├── application.py │ │ │ ├── home.py │ │ │ ├── sidebar.py │ │ │ └── unowned_wiki_list_exporter.py │ │ └── test │ │ ├── test_application.py │ │ ├── test_home.py │ │ ├── test_sidebar.py │ │ └── test_unowned_wiki_list_exporter.py │ └── ruby │ ├── README.md │ ├── Rakefile │ ├── exec │ │ ├── export_unowned_wiki_list.rb │ │ └── update_wiki_list_on_home_and_sidebar.rb │ ├── src │ │ ├── application.rb │ │ ├── home.rb │ │ ├── sidebar.rb │ │ └── unowned_wiki_list_exporter.rb │ └── test │ ├── application_test.rb │ ├── home_test.rb │ ├── sidebar_test.rb │ └── unowned_wiki_list_exporter_test.rb └── update_wiki_list_on_home_and_sidebar.sh 10 directories, 34 files
4-1. Application クラス
Application クラスでは以下の処理を行います。
- 全ての Wiki 一覧を
Arrayで取得し、昇順でソートし./Home.mdと./Sidebarを除いてキャッシュします。 - Wiki の行頭を読み込み Owner 名を読み取り、
{ '@test-owner' => ['Owner記名ありページ.md'], 'Ownerチームが不明だが必要なページ群' => ['Ownerチームが不明だが必要なページ.md'], 'Ownerチーム・要or不要が不明なページ群' => ['Ownerチーム・要or不要が不明なページ.md'], 'Owner記名なし' => ['Owner記名なしページ1.md', 'Owner記名なしページ2.md'] }のマッピングをキャッシュします(以下、マッピング①)。 - マッピング①から Ownership なし Wiki 群(以下、マッピング②)を生成・キャッシュします。
4-2. UnknownWikiListExporter クラス
UnknownWikiListExporter クラスでは以下の処理を行います。
実装の説明の都合上、「Owner: @OWNER_TEAM の記名がされていなかった Wiki に全て Owner チームが記入された場合」を前提とします。
- 定数で Ownership なし Wiki 群の3分類を配列で定義します。
- Ownerチームが不明だが必要なページ群
- Ownerチーム・要or不要が不明なページ群
- Owner記名なし
- 1 の定数とマッピング②のキーの差集合を求め、その解に対する件数を「0件」をした配列を計算します。
- マッピング②の分類と対応する Wiki の件数から
'分類: ◯件'の形式の文字列の配列を作り、それに 2 で求めた配列を結合します。 - 文字列を
./unowned_wiki_count_list_by_namespace.txtに書き込みます。- Python では配列の書き込みが出来ないため、要素を結合して
strに変換した上で書き込みを行います
- Python では配列の書き込みが出来ないため、要素を結合して
4-3. 実行ファイル
実行ファイルでは以下の処理を行います。
UnknownWikiListExporterクラス(Ruby/Python) をインポートします。- 処理開始の旨を標準出力で通知します。
- 分類ごとの件数を標準出力で通知します。
./unowned_wiki_count_list_by_namespace.txtに分類ごとの件数を書き込みます。- 実行結果を標準出力で通知します。
- 処理完了の旨を標準出力で通知します。
5. GitHub Actions 実装
5-1. Update Wiki List on Home and Sidebar
Update Wiki List on Home and Sidebar では以下の処理を行います。
- 本体リポジトリをチェックアウトしソースコードを取得します。
- Wiki リポジトリをチェックアウトし Wiki 群を取得します。
- Git 操作を行う上でのユーザー情報(ユーザー名・メールアドレス)を設定します。
- ユーザー名は GitHub Actions による実行であることが分かるような任意のユーザー名を設定します
- メールアドレスは Dependabot などの Bot ユーザーに対して付与する有効なメールアドレス
41898282+github-actions[bot]@users.noreply.github.comを設定します(Ref. GitHub Actions bot email address?#26560)
- Wiki リポジトリのディレクトリに移動します。
- 実行コマンドとその引数をバックトレース情報として出力しつつ(
-x)、エラーが発生したら即時処理を終了する(-e)シェルの設定を行います。 - Wiki リポジトリの最新状態を取り込みます。
./update_wiki_list_on_home_and_sidebar.shを叩いて ruby/update_wiki_list_on_home_and_sidebar.rb の実行ファイルをコールし、./Home.mdと./_Sidebar.mdを更新します。- 差分をコミット&プッシュします
git log --oneline -1で取得した直前のコミットメッセージに識別子である 8 取得したタイムスタンプが含まれていれば GitHub Actions による更新ありとみなし、この場合のみ Slack 通知処理に進みます。--onelineはコミットの詳細情報のうち、コミットハッシュとコミットメッセージのみを1行表示するためのオプションです-Nは直前の N 個のコミットを表示するためにオプションですon:scheduleの Cron Workflows はon: pushとは異なりプッシュイベントに Hook して発火していないので、github.event.head_commit.messageを参照しても空文字が返るためこの方法を採用しています
- Payload に指定した JSON リクエストボディのメッセージを指定の Slack チャンネルに通知します。
5-2. Notify Unowned Wiki List
Notify Unowned Wiki List では以下の処理を行います。
- Wiki リポジトリをチェックアウトし Wiki 群を取得します。
- Git 操作を行う上でのユーザー情報(ユーザー名・メールアドレス)を設定します。
- Wiki リポジトリのディレクトリに移動します。
- 実行コマンドとその引数をバックトレース情報として出力しつつ(
-x)、エラーが発生したら即時処理を終了する(-e)シェルの設定を行います。 - Wiki リポジトリの最新状態を取り込みます。
./exec/export_unowned_wiki_list.shを叩いて./unowned_wiki_count_list_by_namespace.txtのテキストファイルを読み込み、Ownership なし Wiki 群ごとの件数を Slack 通知で使う Payload の JSON レスポンスボディで展開出来るように環境変数に保持します。- bash における配列の扱いがとても難しかったです
- Payload に指定した JSON リクエストボディのメッセージを指定の Slack チャンネルに通知します。
6. 実行結果・差分
6-1. Ownership なし Wiki 群の分類ごとの件数エクスポートスクリプト
このテキストファイルの情報を、月曜日の 09:00 に Notify Unowned Wiki List を実行し、Ownership なし Wiki 群の分類ごとの件数を指定の Slack チャンネルに通知します。
実行結果
$ bash exec/export_unowned_wiki_list.sh ==================== Exporting Unowned Wiki List... ==================== Here is the result: Ownerチームが不明だが必要なページ群: 1件 Ownerチーム・要or不要が不明なページ群: 1件 Owner記名なし: 2件 Check it out result on '../../unowned_wiki_count_list_by_namespace.txt' !! ==================== Done Exporting Unowned Wiki List 🎉 ====================
出力テキストファイル
Ownerチームが不明だが必要なページ群: 1件 Ownerチーム・要or不要が不明なページ群: 1件 Owner記名なし: 0件
6-2. Ownership ごとの GitHub Wiki の Home と Sidebar 上の Wiki 一覧化 GitHub Actions

6-3. Ownership なし Wiki 群の分類ごとの件数通知 GitHub Actions

7. 「スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう!」本編の最後に
古の時代から現在まで多くのチームやメンバーが相乗りで Snippets 置き場として活用している GitHub Wiki を、Ownership 不在かつ要・不要が不明な玉石混交な状態から Ownership 単位で Wiki が整理された状態を、以下の手順を踏んで実現しました。
振り返ると、まあ順当な手順を無理なく踏んだと思います。
これでソースコードと同等程度の保守性は担保できたのではないかと思います。
あとは運用を続けていく中で、実行頻度や通知メッセージを最適化していけば良いでしょう。
ソースコードもドキュメントも、中長期的な目線で品質を高いレベルで担保し続けるために重要なのは Ownership の明確化 と属人性の排除に尽きると考えます。
前者は、誰が何に責任を持つかを明らかにすることで良い意味で緊張感を持って品質担保に腐心する力学を働かせるため必要です。
後者は、一生懸命泥臭い仕事をしてくれるメンバーやスマートに仕組み化をしてくれるメンバーがいるうちは良いですが、そういったメンバーが抜けても運用が滞りなく回り続ける組織であり続けるために必要です。
私自身、上記のことを意識して技術力研鑽と私自身の働き方の改善に勤しみ続けたいと思います。
8. 次回予告
次回は「【番外編】スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう! Vol.4 -しくじり編-」をお届けします!
GitHub Wiki には本体リポジトリにおけるファイルの History に相当するページの Revisions という編集履歴の概念が存在します。
今回の自動化における作業の過程でそれをほぼ丸ごと吹っ飛ばすという大失敗をしました。
それを解決し自動化を何とか完遂出来ました。
どのように Revisions を吹っ飛ばしたのか、それをどのように復旧し最新の履歴も反映し前方互換性を保った状態まで持っていったかを共有します(いわば、「しくじり先生」回です)。
次回が「スクリプト言語と GitHub Actions で GitHub Wiki に秩序をもたらそう!」シリーズの最終回です。
それではまた、お楽しみに 👋🏻
9. バックナンバー
blog.studysapuri.jp blog.studysapuri.jp blog.studysapuri.jp blog.studysapuri.jp blog.studysapuri.jp