Azure PipelinesのタスクでPullRequestに対してコメントをつけたい

要約
-------

以下を試しました

https://stackoverflow.com/questions/60048492/how-to-create-a-comment-in-azure-devops-pr-in-case-of-build-failure


モチベーション
---------

インフラをコードで管理するとなると、インフラに適用した時どうなるかが気になります。
そんなときはterraform planやAzure TemplateのWhat-ifで適用時の変化を確認することができます。

これをPullRequest時に行ってレビュー時に一緒にチェックできると嬉しいでしょう。手元に持ってきて実行するのも手間なのと、そもそも実行できない環境の場合もあるためです。

今回はAzure PipelinesでAzure TemplateのWhat-ifの結果をコメントに残すのを行ってみました。


手順
-------------

### Pipelinesを仕込む

ブランチのポリシーでPRトリガを設定するにはあらかじめPipelineが存在する必要があるため、先に行っておく必要があります。

適当なタスクでいいのでazure-pipelines.ymlを書き、Gitリポジトリ保存し、Azure Pipelinesに登録してPipelinesが動くようにしておきます。

通常のPipelineの登録と同じなので省略します。



### ブランチのポリシーでPRトリガを実行するようにする

[過去に書いた](https://qiita.com/fukasawah/items/78b32057354ae8ba4690)り、[ドキュメントにも](https://docs.microsoft.com/en-US/azure/devops/repos/git/branch-policies?view=azure-devops&tabs=browser#build-validation)ありますが一応。

Reposの該当のリポジトリを開き、Branchesから対象のブランチのBaranch Policyを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52527/b7185509-3ba2-7029-7319-a9db4baad588.png)

ブランチの設定が開くので、「＋」ボタンからBuild Validationを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52527/97e27b38-7335-f269-ad4c-d45415309a50.png)

あらかじめ登録しておいたpipelineを指定し、Saveで保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52527/61642f23-6b40-0b84-0571-955d25a95371.png)

以上で完了です。


### リポジトリの設定でBuild ServiceからのPullRequestのContributeを許可する

プロジェクト毎にBuild Serviceユーザ（`{Project名} Build Service ({Organization名})` ）がいるようで、Pipelineを実行しています。
なので、そのユーザに対してPullRequestへのContributeを許可します。デフォルトは不許可になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52527/75f68293-3b00-0c1a-4230-3d60433f5c7f.png)


これを行わないと後述するPullRequestのスレッドにコメントをつけるREST APIで以下のようなレスポンスになり失敗します。

```json
{
  "$id": "1",
  "innerException": null,
  "message": "TF401027: You need the Git 'PullRequestContribute' permission to perform this action. Details: identity 'Build\\xxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxx', scope 'repository'.",
  "typeName": "Microsoft.TeamFoundation.Git.Server.GitNeedsPermissionException, Microsoft.TeamFoundation.Git.Server",
  "typeKey": "GitNeedsPermissionException",
  "errorCode": 0,
  "eventId": 3000
}
```

### PipelinesでPull RequestのスレッドにコメントをつけるRest APIを実行するタスクを書く

`$(System.AccessToken)`を`Authorization`ヘッダにBearerトークンとして埋め込み、Rest APIに対してJSONをPOSTするだけです。Rest APIの仕様は以下です。

https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-threads/create?view=azure-devops-rest-6.0

ただ、JSONの組み立てやMarkdownの記入が地味に面倒なので、PowerShellかPython等でPipelines用のスクリプトを用意してGitリポジトリに含めておき、それを実行するのがよいでしょう。

参考までに標準入力をコメントにするスクリプトを書いたので共有です。

https://gist.github.com/fukasawah/08d351219799f8102b1108fcd3603e56

タスクの例は以下です。


``` yaml
# 単純なBash Script
- task: Bash@3
  condition: eq(variables['Build.Reason'], 'PullRequest')
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
  inputs: 
    targetType: inline
    script: |
      (echo "# Hello"; echo "world") | python pull-request-comment.py

# Azure CLIでBicepをWhat-ifにかけた結果をコメントする
# （別途Service Connectionの設定をしてPipelinesからAzure CLIタスクでAzureリソース操作ができるようにしておく）
- task: AzureCLI@2
  condition: eq(variables['Build.Reason'], 'PullRequest')
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
  inputs:
    azureSubscription: "Service Connection名"
    scriptType: bash
    scriptLocation: inlineScript
    inlineScript: |
      az deployment group what-if --subscription "$(Subscription)" --resource-group $(ResourceGroup) --template-file $(TemplateFilePath) 2>&1 | python pull-request-comment.py --title "What-if"
```


- ` $(System.AccessToken)` で環境変数を埋め込むこと
- `condition: eq(variables['Build.Reason'], 'PullRequest')` でPRのみ反応するタスクとして定義

**特にアクセストークン`$(System.AccessToken)`の扱いには注意しましょう。これが漏れると誰でもBuild Serviceの権限でできることができてしまいます。**

また、PullRequestのコメントに残ることを考え、出力するものは問題ないかどうかは確認しておきましょう。

動作確認
-------------

### PullRequestを行い、Pipelineをトリガする

うまくいくとPRにコメントが付きます。

上記の「Azure CLIでBicepをWhat-ifにかけた結果をコメントする」のタスクだとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52527/1993270a-a2f6-e433-c1b0-53af22af7c5a.png)

PRの内容を適用すると、ストレージアカウントが1個作られる様子がわかります。よかったですね。
（ただ、What-ifについてはこんな単純な差分ばかりではないのが現実で、この結果を確認するほうが辛くなってきそうですが。）