パイプラインの成否を即座にBranch Protectionに使いたい
2023-06-05
azblob://2023/06/05/eyecatch/2023-06-05-branch-protection-status-check-000_0.jpg

こんにちは、新卒の小野です。

今回は、技術研修の中で使ったGitHubのBranch Protectionについて思うところがあったのでそれについてと、ついでに代替案について書いていきたいと思います。

Branch Protectionとは

簡単に言えばPull Requestをマージしようとする際の制限で、例えばN人(Nは0~6の整数)以上のApproveを必須にしたり、コミットに対してパイプラインの成否等のステータスがすべて成功であることを要求したりがあります。

本題

Pull Requestをマージする前には、上記の通りパイプラインがすべて成功していることを、ステータスとして要求することができるので、Azure Pipelinesの設定をしていざ、これを必須にするぞと設定ページを開くと困ったことが起こりました。

No status checks found

設定したパイプラインが出てこないのです。しかし画像のなかにもちゃんと理由を書いてくれていますし、Azure Pipelines側のドキュメントにも同様のことが書いてありました。

パイプラインが過去1週間に少なくとも1回実行されていないと出てこない
https://learn.microsoft.com/ja-jp/azure/devops/pipelines/repos/github?v…

どうやら、1度も実行されていないパイプラインを条件にすることはできないようです。

なので、

  1. 最低でも1回はpushしてパイプラインが実行されるのを待つ
  2. 手動で強制的に実行させる

のどちらかを手段をとることになります。しかし、1つ目ではパイプライン完了前にマージできてしまうし、2つ目も手動で実行はあまりスマートではありません。

代替案

設定方法

このステータスチェック、実はパイプライン以外も条件にすることができます。

GitHubのドキュメントのここからREST APIを確認することができます。

リンク先のcurlコマンドのサンプルを貼っておきます。

コードブロック内の¥は\と読み替えて下さい。

curl -L \
  -X PATCH \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/branches/BRANCH/protection/required_status_checks \
  -d '{"strict":true,"contexts":["continuous-integration/travis-ci"]}'

詳しく中身を見ていきましょう。

まずパスについてです。

https://api.github.com/repos/OWNER/REPO/branches/BRANCH/protection/required_status_checks 

大文字の部分がパラメータになります。

  • OWNER: リポジトリの所有者です。
  • REPO: リポジトリ名です。
  • BRANCH: ステータスを要求したいブランチ名です。

ここまでは分かりやすいというか自明ですね。続いてbodyについてです。

  • strict: ステータスが最新のコードについてであることを要求する。boolean型
  • contexts: 要求するステータス名の配列。 ただし非推奨 stringの配列型
  • checks: 要求するステータスの配列。objectの配列型
    • context: 要求するステータス名 必須 string型
    • app_id: ステータスの提供元、省略すると自動で設定されます。 integer型

サンプルではcontextsを使用していますが、これは非推奨となっているのでchecksを使うのがよいでしょう。

これでrepoのスコープが許可されたTOKENを含めてcurlコマンドを実行することで、マージ前にcontextで指定した名前のステータスがチェックされることを要求することができます。

チェックする方法

これでステータスがチェックされることを必須にすることができるようになりましたが、このままではいつまで経ってもチェックされず、マージすることができません。

ステータスを変更するためのREST APIのドキュメントはここです。

またリンク内のcurlコマンドのサンプルを貼っておきます。

先ほどと同様にコードブロック内の¥は\と読み替えて下さい。

curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/statuses/SHA \
  -d '{"state":"success","target_url":"https://example.com/build/status","description":"The build succeeded!","context":"continuous-integration/jenkins"}'

詳しく中身を見ていきます。

まずパスについてです。

https://api.github.com/repos/OWNER/REPO/statuses/SHA

これについても大文字の部分がパラメータです。

  • OWNER,REPO: 先ほどと同じです。
  • SHA: ステータスを変更するコミットのハッシュです。

続いてbodyを見ていきましょう。

  • state: ステータスの状態、successにするとチェックされた状態になる。必須  error, failure, pending, success のいずれか
  • target_url: ビルド出力のフル URL 。GtiHub上のREADME等で状況を表示することができます。 string型(URL) or null
  • description: 概要 string型 or null
  • context: ステータスを書き換えたい名前。デフォルトではdefaultという文字列になる。 string型

これでrepoのスコープが許可されたTOKENを含めてcurlコマンドをパイプライン内で実行することで、contextで指定した名前のステータスを変更することができます。

また、こちらの場合はcurlだけでなく、GitHub CLIを使うこともできます。

メリット・デメリット

メリット

この方法とる場合の1番のメリットはコマンドで定義できることでしょう。

今回はステータスチェックの部分のみをREST APIで定義しましたが、GitHub APIにはBranch Protectionをすべて一括で設定するAPIも用意されているので、これを使えば、完全にコマンドだけでBranch Protectionを設定することができます。

また、最初の目的通り1回pushするなり手動なりでパイプラインを実行せずとも即座に反映させることができます。

ほかにも、パイプラインの処理の途中でステータスを書き換えるコマンドを実行することで途中経過を反映させることもできます。

パイプラインと完全に同期しないので、チェックポイント代わりにここまでは実行できたというのが管理画面に行かなくても、一目でわかるようにできるのも一応メリットとしてもいいでしょう。

デメリット

一方でもちろんデメリットもあります。

特に大きいのはトークンの扱いでしょうか。

最初に定義する際に使うのはともかく、パイプライン側で継続的に使うには注意が必要です。

有効期限を永続にするのはもちろん避けなければなりませんが、期間を区切ると突然使えなくなる恐れがあります。

GitHub  Actionsであればsecrets.GITHUB_TOKENという変数が用意されており、それを使えば解決できるのですが、Azure Pipelinesではそうもいきません。

ドキュメントを見ている限りは、Azure Pipelines GitHub Appを認証することでこれは解決できそうなのですが、ちょっと方法がわからなかったのでわかり次第またブログにしたいと思います。

そしてもう一つのデメリットが、このチェックはパイプラインの成功を保証するものではないということです。

もちろん正しく使えば保証できるのですが、例えばステータスの名前をタイポしていたり、状態を書き換える処理の場所を間違えたりすると、ステータスチェックが信用ならないものになってしまいます。

最後に

最初は"絶対こっちの方が楽だろ"の気持ちでブログを書き始めたのですが、設定方法の部分はともかくステータスのチェックはちゃんと調べると、トークン周りが意外と厄介でした。個人的なリポジトリはGitHub Actionsで使ってたので、ほかではここまで困るとは思っていませんでした。

これだと、おとなしく1回pushするなり、手動で実行して反映させる方がいい気がしてきました。

それでも途中経過をチェックポイントとして表示したいというようなことなどがあれば、思い出してもらえればと思います。