AzurePipelineでloop(繰り返し)処理を実装する

# はじめに

[AzurePipeline](https://azure.microsoft.com/ja-jp/services/devops/pipelines/)はビルドとデプロイのpipelineを作成できるサービスです。yaml形式で配列やハッシュなどの表現ができたり、Ansibleのjinja2テンプレートの様に直接テンプレートにforeachループやif文を記載できたり直感的な実装ができます。

```yaml
paramters:
  replaceParams: []

jobs:
  - job:
    displayName: test job
    pool:
      vmImage: ubuntu-18.04
    steps:
      - ${{ each param in parameters.replaceParams }}:
          - script: echo hi ${{ param }}
```

今回、直感的と言いつつloop処理を実装しようと思ったら、仕様にハマる箇所がたくさんあったのでメモします。
また一度慣れてしまえば直感的ですが、慣れるまでの習得に時間がかかり、デバッグは実行時のエラーが汎用的な情報が少なくトラブルシューティングが難しいことがよくあります。
ドキュメントをちゃんと読んで総合して仕様を判断できれば実装はスムーズにできたと思うのですが、初学者には少し難しく感じましたので、公式ドキュメントを噛み砕いた記事を以下に記します。

※[最終的に作成したテンプレートはこちらを参照ください](https://gist.github.com/kita-atsushi/99657eaa83b4158df418c3869e48f8e3)。

## loop処理の注意点

まずは公式ドキュメントを確認しますが現状ではloop処理についてストレートに記載されているものがなく、ヒットするのは以下です。

* [Extend from a template](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops#extend-from-a-template)

このドキュメントはpipelineを示すtemplateから他のtemplateを呼び出す一例を示す記載です。
そのため仕様としてloop処理を実装するための説明はありません。また、AzurePipelineの公式Githubにloop処理が例として["Each" Template Expression](https://github.com/microsoft/azure-pipelines-yaml/blob/master/design/each-expression.md)に記載されていますが、愚直に実装しても動かすことができず、さらにエラーメッセージも`While parsing a block mapping, did not find expected key`という汎用エラーが出てしまい、どういった実装正しいか、イマイチどう書けば良いか迷子になってしまいます。

結局、このloop処理の仕様は調べても良い情報なく、templateがtemplateを呼び出す場合だけに**呼び出されるtemplateにloop処理が使用できる**、と、動作ベースで理解しました。仕様ははっきり把握できませんでしたが。。

## loop処理させるために渡す配列の注意点

loop処理の実装方法がなんとなく分かったら、以下の様にしてtemplateにparamtersに配列でパラメータを渡したくなりますが、これも動作しません。[AzurePipelineのParameterとして配列は渡せません](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops#parameter-data-types)。Listとして渡せるのはAzure Pipelineで扱うstepListやjobListなどもう少し単位の大きい構造を持ったパラメータのようです。

```yaml
# ci.yml(呼び出す側template)
jobs:
  - template: ./my-template.yml
    parameters: ["taro", "hanako"]

# my-template.yml(呼び出される側template)
parameters:
    keys: []

jobs:
  - job:
    displayName: test job
    pool:
      vmImage: ubuntu-18.04
    steps:
      - ${{ each key in parameters.keys }}:
          - script: echo hi ${{ key }}
```

またちなみに以下のようにvariablesに配列を定義してもtemplateが不正とエラーになります。この場合[variablesは文字列しか定義できない、という仕様](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema#variables)にひっかかります。[フォーラムでも同じ質問がされていました](https://developercommunity.visualstudio.com/content/problem/812728/variables-in-yaml-pipeline-are-not-allowing-to-def.html)のでこちらも参照ください。

```yaml
# ci.yml(呼び出す側template)

variables:
  paramList: ["taro", "hanako"]

jobs:
  - template: ./my-template.yml
    parameters: $(paramList)
```

## 注意点を踏まえての実装

上記で説明した注意点を踏まえると以下の様に呼び出される側templateで配列を指定するか、呼び出す側でstepListまたはjobListとしてparamterを渡す方法のどちらかになります。

```yaml
### ci.yml(呼び出す側template)
jobs:
  - template: ./my-template.yml

### my-template.yml(呼び出される側template)
parameters:
  replaceParams:
    - "aaa"
    - "bbb"
    - "ccc"

jobs:
  - job:
    displayName: test job
    pool:
      vmImage: ubuntu-18.04
    steps:
      - ${{ each param in parameters.replaceParams }}:
          - script: echo hi ${{ param }}
```

これを実際に動かすと以下の様になります。

![スクリーンショット 2020-02-08 20.00.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133866/a45fb05a-b977-bda9-64d3-5c3525664cec.png)

上記パイプラインで繰り返し処理を実装できました。
次はstepList、jobListを利用してもう少し汎用的な処理にトライしてみたいと思います。

以上