Azure DevOps(に限らないですが)通常のCI/CDではだいたいこんな感じですよね。

一応Service Connectionが使えるパイプラインは管理者が指定できますが、一度許可で指定したらパイプラインの内容がどうあれ、デプロイし放題です。最初にマルウェアチェックのタスクやソース監査のタスクがあっても「めんどくさいからいらないや」とパイプライン編集権限のある人削除してしまえばなんでもデプロイできるようになってしまいます。
これはまずいので、Service Connection(Environmentsでも同じ)を使うときにチェックしたい、という要件が必要になります。

これを実現するための機能がApproval and checksです。
承認にはいくつかの種類があるのですが、わかりやすいのはBusiness Hours(使用可能な時間を制限)、Approvals(承認しないとデプロイできない)です。しかし、承認者がいつも正しくチェックできるとは限りません。そういうチェックは機械にやらせましょうということで、使えるのがRequired templateやEvaluate artifactです。後者はまた別途解説します。
特にRequired templateは特定のテンプレートが含まれていないとService ConnectionとEnvironmentsを使わせないという設定なので、極めて有効です。しかし、この機能ドキュメントも試しただけではわかりづらい仕様があります*1。
まず、テンプレートのおさらいです。呼び出されるテンプレートをPipelinesレポジトリのapprvaldemo/credscan.ymlに配置します。
steps:
- task: CredScan@2
displayName: 'Check Cred scan'
inputs:
toolMajorVersion: 'V2'
わかりやすくするために引数などは使わないテンプレートです。そして、そのテンプレートを使用する呼び出し側。resources句でGitレポジトリをリソースとして参照して、templateという名前でPipelinesレポジトリを参照可能とします。
resources:
repositories:
- repository: templates
type: git
name: SessionDemo/Pipelines
stages:
- stage: Deploy
jobs:
- deployment: Deployment
displayName: 'Deploy'
pool:
vmImage: 'windows-latest'
strategy:
runOnce:
deploy:
steps:
- template: apprvaldemo/credscan.yml@templates
最後の-templateでテンプレートとして用意されている外部レポジトリのYAMLファイルを参照しています。この例では非常に単純なタスクですが、一連のデプロイやビルド方法、特定のコンプライアンス関係のタスクをまとめて使用するのが一般的かと思います。

じゃあ、このテンプレートをApprovalに設定すればいいのか?こういう形にしたいということで、このテンプレートをApproval and checksに指定したとします。

これで良さそうに見えるので実行してみると…。


ビルドが失敗します。チェックが失敗したとかではなくて、そもそもApproval and checksにRequired templateを指定するときはこのようにYAMLパイプラインの一部として存在するテンプレートの指定は認められていません。評価の順番としてはこうなるそうです。
- テンプレートを展開
- 式を評価
- stageを評価して依存関係をチェック
- すべてのリソースに関して承認をチェック (以下略)
で、この「テンプレートを展開」ですが、何もしなくても展開ではなくて、extends句を使った展開でなくてはなりません。実際にはこうなります。
呼び出される側のテンプレートはこの方法で、template.ymlという名前でGitレポジトリのルートフォルダーに保存します。
parameters:
- name: buildConfiguration
type: string
default: Release
steps:
- task: DownloadBuildArtifacts@1
inputs:
buildType: 'specific'
project: '0a9af6d9-(略)'
pipeline: '238'
specificBuildWithTriggering: true
buildVersionToDownload: 'latestFromBranch'
allowPartiallySucceededBuilds: true
branchName: 'refs/heads/main'
downloadType: 'single'
artifactName: 'approvaldemo'
downloadPath: '$(System.ArtifactsDirectory)'
- task: AzureWebApp@1
inputs:
azureSubscription: '(Service connection)'
appType: 'webApp'
appName: '(appname)'
package: '$(System.ArtifactsDirectory)/**/*.zip'
deploymentMethod: 'auto'
- project: Azure DevOps内のプロジェクトのGUID。REST APIでも使います。
- pipeline: パイプラインのIDを指定します。こちらは連番ですね。
GUIではこのように指定します。

Download Build Artifactsタスクの指定例です。GUIではプロジェクト名とパイプライン名が名前で選択できるので、楽です。そして、呼び出すパイプラインはこのように指定します。
pool:
vmImage: ubuntu-latest
variables:
buildConfiguration: 'Release'
extends:
template: templatefile.yml
parameters:
buildConfiguration: $(buildConfiguration)
extendsでテンプレートを指定します。これでこのパイプライン内で指定したテンプレートが完全に展開されます。Approval and checksへの指定はこうなります。


Service Connectionですが、Environmentでも同じです。
| 項目 | 説明 |
|---|---|
| repositry | プロジェクト名/レポジトリ の形式で指定します |
| ref | Gitのブランチ名です。ref/heads/ブランチ名 です。デフォルトをmainにしている人は気を付けてください |
| Path to required YAML template | YAMLテンプレートファイル名を指定します。私はサブフォルダー指定したら動きませんでした |
Approval and checksを保存すると、指定したテンプレートを使わない限り、Service Connectionへのデプロイに失敗します。

先ほどのエラーはApproval and checksの書式間違いですが、こちらはちゃんとApproval and checksのポリシー違反であることが分かります。
注意事項(今後変わるかもしれない)
templateに指定するテンプレートのYAMLはGitのルートフォルダに置かれていなくてはなりませんでした。サブフォルダに置くと「Gitのコミットにない」といわれました。もしかしたらバグかもしれない。
パイプラインやapproval and checksを編集すると、挙動がおかしいように見えるので、聞いてみます…。
一応シナリオとしては書いたようにセキュリティのチェックをするためのに用意されています。テンプレート内で独自のフローを使いたければ、引数に渡して、stepsとして展開する、という方法が紹介されています…が、自分が試したときはresourcesで外部レポジトリのフォルダー使うと、Queueに入ろうとして、エラーになったのだけどなぁ🤔。
*1:最初に試したときはこうじゃなかったはずなんだけどなぁ…