Azure Pipelineのtemplateを利用して、slotの有無に関わらずapp service へのデプロイ処理を共通化する

## はじめに
[LIGTHzアドベントカレンダー 2022](https://qiita.com/advent-calendar/2022/lightz)の12日目の記事です。
この記事では、Azure Pipelineのtemplateを利用して、slotの有無に関わらずapp service へのデプロイ処理を共通化する方法を記載します。
まずはslot,templateについて簡単に説明した後、pipelineのサンプルコードを記載します。

## App serviceのslot機能とは
App ServiceプランのサービスレベルがStandard以上で利用できる機能です。
こちらの機能を利用することで、blue-green deploymentできたりといくつかのメリットがあります。
詳しくは[公式](https://learn.microsoft.com/ja-jp/azure/app-service/deploy-staging-slots)を参照

## Azure Pipelineのtemplateとは
templateを利用するとPipeline上での再利用可能なコンテンツ、ロジックなどを定義することができます。
例えば、デプロイ処理で複数環境(開発、ステージング、QA、本番...etc)が存在した場合でもtemplate内にデプロイ処理を記載すれば各環境で使いまわしができるといった感じです。
詳しくは[公式](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/templates?view=azure-devops)を参照

## やりたいこと
以下のapp service環境へのデプロイを共通化する
- 開発環境
    - Shared Plan等、slot機能のないプラン

- 本番環境
    - Standard Plan以上のslot機能を有したプラン
    - pipelineから対象のslotへデプロイ後、[approvals and checks](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops&tabs=check-pass)機能等を使い、運用slotへスワップされることを想定


### サンプルコード
ディレクトリ構成、サンプルコードは以下を通り。
※azure-pipelines.ymlは deploy stageのみを抜粋

```
azure-pipelines.yml
pipeline_templates\deploy.yml
```

``` yaml:azure-pipelines.yml(抜粋)
stages:
  - stage: DeployDev
    displayName: DeployDev
    dependsOn: Build
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/dev'))
    jobs:
      - template: pipeline_templates/deploy.yml
        parameters:
          azureSubscriptionEndpoint: '[YourSubscriptionEndpoint]'
          webAppName: '[YourAppName]'

  - stage: DeployProd
    displayName: DeployProd
    dependsOn: Build
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - template: pipeline_templates/deploy.yml
        parameters:
          azureSubscriptionEndpoint: '[YourSubscriptionEndpoint]'
          webAppName: '[YourAppName]'
          slotName: '[YourSlotName]'
```
**※`[Your...]`の箇所は各環境の設定が入る**


``` yaml:deploy.yml
parameters:
- name: azureSubscriptionEndpoint
  type: string
  default: ''
- name: webAppName
  type: string
  default: ''
- name: slotName
  type: string
  # slotを持たない環境=productionが既定値となる
  default: production

jobs:
- job:
  steps:
    - checkout: none
    - download: current
      artifact: drop
    
    - task: AzureWebApp@1
      displayName: Deploy Azure Web App
      inputs:
        azureSubscription: ${{parameters.azureSubscriptionEndpoint}}
        appType: webApp
        appName: ${{parameters.webAppName}}
        package: $(Pipeline.Workspace)/drop/$(Build.BuildId).zip
        slotName: ${{parameters.slotName}}
```

[公式](https://learn.microsoft.com/ja-jp/azure/app-service/deploy-azure-pipelines?view=azure-devops&tabs=yaml#deploy-to-a-slot)にもある通り、**slotNameの既定値は production**となります。そのため、デプロイ対象のapp serviceがslotを有していなければ、production slotにデプロイされるように設定しました。

## おわりに
今回はapp serviceのデプロイ方法を記載しましたが、functions用の[task](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-function-app-v1?view=azure-pipelines)を利用することでfunctionsへのデプロイも共通化できると思います。
ただ現状の設定だと、slot設定のあるapp serviceを追加する際に `slotName`の記載を忘れてしまうとproduction slotへ直接デプロイされてしまうというリスクがあるため、本番利用する環境への初回デプロイは事前検証を行うなどの対応が必要と思います。