GithubAppをBearerTokenで認証してDanger-js使ったらハマった話

TL;DR

  • Statusの更新(/repos/:org/:repo_name/statuses/:hash)
  • コメント直前で403吐いて落ちる
  • Docに書いていないフラグを環境変数で渡したら通らないAPIへのRequestをやめて、CommentもPostされた
  • export DANGER_GITHUB_APP=1

Danger-jsとは

2018/2/22現在、調べればいっぱい出てくるので簡単に。

Dangerは、静的解析の結果を整形してBotにReviewさせるツール。
ruby版が大元で他の言語も(https://github.com/danger/danger)

  • Assigneeがいない
  • 変更された行数が多すぎるから分割してくれ
  • src内の変更あるのに*.spec.*が変更・作成されてないよ

とか、LinterやAutoTestで弾け無いような部分も扱うことが出来る。
今回は上記の様な設定を作った。

GitHub App/BearerTokenでの認証

今回はCircle CI上で動かした。
Danger側でCIを判断してくれて、必要な部分のENVを持ってくるので
ぶっちゃけどのCIでも一緒だと思う。

Docの「さあやるぞ(意訳)」をみると、

  • ブラウザのプライベートセッション使って
  • 普段使いとは別の新規アカウントをつくり
  • Personal access tokens(https://github.com/settings/tokens/new)から:reposcopeに権限全部渡して!(privateリポジトリの場合)

確かにそうすると、AccessTokenが一意に決定するので
公式DocのCI設定のように実行環境のEnvに直接DANGER_GITHUB_API_TOKENとして渡せば、後は良しなにやってくれるけど
流石に、このためにUserわざわざ作るのもあれなので、GithubAppとして実行することにした。

方針

アカウント作成の場合と違い、
GitHub Appの場合、先述のDANGER_GITHUB_API_TOKENを動的に取得する必要がある。

今回、リポジトリ内の言語を統一するということもあり
rubyではなくjsを選択したので、Github Developerのサンプルコードを真似て、JWTでTokenを取得する処理を作った。

DANGER_GITHUB_API_TOKEN=$(node ./.bin/getToken.js) danger ci

なので、最終的にこんな感じに実行すればいいだろという方針。

JWT生成してtokenを取得するコード

サンプルコードとの違いは、ruby→jsの言語以外に

  • pemじゃなくてENV経由でprivateKeyを渡す
  • GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...な感じ
  • tokenを取得して標準出力に渡すところまでやる(楽だから)

  • GITHUB_APP_PRIVATE_KEY
  • GITHUB_APP_INSTALLATION_ID
  • GITHUB_APP_APP_ID

CI側のEnvには、上記3つを設定する。
direnvでも使って、
試しにlocalで実行出来るようにすると良い。
実際に実行してみてTokenが帰ってきたらOK。ここまでは簡単。

Circle CIで動かしてみる

config.ymlに適当にrunを追記する

動かない。DEBUG=*を仕込むと、
Dangerfileの構文解析に埋め尽くされた大量のログが吐かれる(※ちゃんとFilterしましょう)
こんな感じに止まっていた。

PRに対する初回投稿は何故か成功(?)しているのに、2度目から全く動かなかった。
どうも、success/failは見える(GithubのPR情報のReadは成功している)いて、
そのあとにGitHubAPIの/userにアクセスして403、
Integration(GitHubApp?)では駄目だと吐いて死ぬ。

何が原因なんだ

それからDangerのDocGitHub Appが利用可能なEndpointのDocResponseで丁寧にこれ読んでよ!って渡してくれてるDocを何10往復もして、数時間溶かして
どうにも動かないので、https://api.github.com/user が何やってるか追った。

とりあえず直前の

No issues or messages were sent. Removing any existing messages.

まではDangerが返してそうなメッセージなのでそれでリポジトリ内を検索。
Executor.tsが引っかかる。
2度目以降のCommentは、this.platform.deleteMainComment(dangerID)
GitHub.tsGitHubAPI.tsgetDangerCommentIDs(dangerID)を呼び、this.getUserIDに…
非常にそれっぽいmethodが来た。これだ。

// https://github.com/danger/danger-js/blob/cdb700a88b174328a28ed37ecaf5f46e0e1bffc9/source/platforms/github/GitHubAPI.ts#L112-L118
getUserID = async (): Promise => {
if (process.env["DANGER_GITHUB_APP"]) {
return
}
const info = await this.getUserInfo()
return info.id
}

403を返す[GET] /user(get-the-authenticated-user)で何やってるのか

Docの「さあやるぞ(意訳)」をみると、ブラウザのプライベートセッション使って、普段使いとは別の新規アカウントをつくり…

もうわかりますね。getUserInfoでは、
ユーザーアカウントがあると仮定して/user叩いてます。
GitHub Appが利用可能なEndpointのDoc読んだ時点で気づくべきなんですが、/userはAvailableじゃないんですよね。
ユーザーとしてコメントする前提ならそりゃそうだ。

で、
DANGER_GITHUB_APPでリポジトリ内検索すると分かるんですけど、
どうやら、CI使わない独立した実行applicationとしてのperilのための設定のようで

token取得部分とか、書いてたやつとまるで同じ
GitHub Appでのやり方もここにあったという…

結論

DANGER_GITHUB_APPがまさに、GitHub Appかどうかのフラグになってるので

DANGER_GITHUB_API_TOKEN=$(node ./.bin/getToken.js) danger ci

ではなく

DANGER_GITHUB_APP=1 DANGER_GITHUB_API_TOKEN=$(node ./.bin/getToken.js) danger ci

になります。グエー。

余談

色々アレだけど、コード書くと益々wpだめだこりゃ

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です