Azure Static Web AppsのCI/CDをAzure DevOpsで構築する！そしてパイプラインをカスタマイズする

Azure Static Web Apps といえば [GitHub Actions を使って CI/CD を行う](https://docs.microsoft.com/ja-jp/learn/paths/azure-static-web-apps/?WT.mc_id=AZ-MVP-5002209)情報が多く出てきます。
しかし、当然ながら Azure DevOps (Azure Pipelines) でも Azure Static Web Apps の CI/CD を行うことができます。

手順は公式ドキュメントで解説されています。この通りに準備すれば良いです。
https://docs.microsoft.com/ja-jp/azure/static-web-apps/publish-devops

# 基本構成
例えば Azure Pipelines で [Blazor WebAssembly](https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?WT.mc_id=DT-MVP-5002209) のアプリを Azure Static Web Apps にデプロイする場合、この YAML ファイルが基本的な構成となります。

```yml:azure-pipelines.yml
trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: AzureStaticWebApp@0
  inputs:
    app_location: '{Blazor WebAssembly のフォルダ}'
    app_build_command: 'dotnet build'
    output_location: 'wwwroot'
    azure_static_web_apps_api_token: '$(deployment_token)'
```

このパイプライン定義の場合、 `AzureStaticWebApp@0` というタスクにおいてアプリケーションのビルドと Azure Static Web Apps へのデプロイが一括して実行されます。
上記のパイプラインを実行したときのログ（一部）はこちらです。

```
Downloading and extracting 'dotnet' version '5.0.400' to '/tmp/oryx/platforms/dotnet/5.0.400'...
Downloaded in 24 sec(s).
Verifying checksum...
Extracting contents...
Done in 27 sec(s).


Using .NET Core SDK Version: 5.0.400

Welcome to .NET 5.0!
---------------------
SDK Version: 5.0.400

Telemetry
---------
The .NET tools collect usage data in order to help us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
  Determining projects to restore...
  Restored /working_dir/Client/swa-devops-blazor.csproj (in 2.01 sec).

Publishing to directory /ss-oryx/app...

Microsoft (R) Build Engine version 16.11.0+0538acc04 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  swa-devops-blazor -> /working_dir/Client/bin/Release/net5.0/swa-devops-blazor.dll
  swa-devops-blazor (Blazor output) -> /working_dir/Client/bin/Release/net5.0/wwwroot
  Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Compressing Blazor WebAssembly publish artifacts. This may take a while...
  swa-devops-blazor -> /ss-oryx/app/
Preparing output...

Removing existing manifest file
Creating a manifest file...
Manifest file created.

Done in 67 sec(s).


---End of Oryx build logs---
Finished building app with Oryx
Using 'staticwebapp.config.json' file for configuration information, 'routes.json' will be ignored.
Copying 'staticwebapp.config.json' to build output
Zipping App Artifacts
Done Zipping App Artifacts
Either no Api directory was specified, or the specified directory was not found. Azure Functions will not be created.
Uploading build artifacts.
Finished Upload. Polling on deployment.
Status: InProgress. Time: 0.3610669(s)
Status: Succeeded. Time: 15.6896716(s)
Deployment Complete :)
Visit your site at: https://xxx.azurestaticapps.net
Thanks for using Azure Static Web Apps!
Exiting

Finishing: AzureStaticWebApp
```

1つのタスクで CI/CD が完結するのでとても便利ではあります。
しかし個人的には CI と CD は区別しておきたいので、ビルドしたものを Azure Static Web Apps にデプロイできるようパイプラインをカスタマイズする方法を整理しました。

# アプリビルドのスキップ
実は Azure Static Web Apps 用のタスクはアプリビルドをスキップすることができます。
タスク構成に `skip_app_build: true` を追加することで、アプリビルドのスキップが有効となります。
ちなみに GitHub Actions でも同様の設定があります。
https://docs.microsoft.com/ja-jp/azure/static-web-apps/github-actions-workflow#skip-app-build

```yml:azure-pipelines.yml
trigger:
- master

pool:
  vmImage: ubuntu-latest

steps:
- task: AzureStaticWebApp@0
  inputs:
    app_location: '{Blazor WebAssembly のフォルダ}'
    app_build_command: 'dotnet build'
    output_location: 'wwwroot'
    skip_app_build: true
    azure_static_web_apps_api_token: '$(deployment_token)'
```

# Blazor WebAssembly のビルドタスクを追加する
次にパイプライン定義にビルドタスクを追加します。（Blazor WebAssembly に限りません。 .NET Core の一般的な構成です。）
ここで注意点として、ビルドタスクで作成したデプロイ用のファイルを Azure Static Web Apps タスクが参照できるようにする必要があります。

Azure Pipelines ではビルドタスクで作成されたファイルを「成果物 (Artifact)」と呼びます。
成果物を保管することに適したディレクトリが用意されており、`Build.ArtifactStagingDirectory` という名前の環境変数でアクセスすることができます。

`Build.ArtifactStagingDirectory` を介してタスク間の成果物を受け渡しを行い、デプロイを行います。

```yml
steps:
- task: DotNetCoreCLI@2
  displayName: Run dotnet restore
  inputs:
    command: 'restore'
    projects: 'blazorwasmapp.csproj'
    feedsToUse: 'select'
- task: DotNetCoreCLI@2
  displayName: Run dotnet build
  inputs:
    command: 'build'
    projects: 'blazorwasmapp.csproj'
    arguments: '--no-restore --configuration Release'
- task: DotNetCoreCLI@2
  displayName: Run dotnet publish
  inputs:
    command: 'publish'
    publishWebProjects: false
    projects: 'blazorwasmapp.csproj'
    arguments: '--no-build --configuration Release --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: false
```

# 完成版
.NET アプリケーションの場合、デプロイ用のファイルは `dotnet publish` コマンドで生成します。
ファイルの出力先 (`--output`オプション) に `Build.ArtifactStagingDirectory` を指定し、`AzureStaticWebApp@0` タスクの作業ディレクトリに `Build.ArtifactStagingDirectory` を指定します。

一通りのパイプライン構成は下記のようになります。
下記には含めていませんが、単体テストの実行を追加するのも良いでしょう。

```yml:azure-pipelines.yml
trigger:
- master

pool:
  vmImage: ubuntu-latest

steps:
- task: DotNetCoreCLI@2
  displayName: Run dotnet restore
  inputs:
    command: 'restore'
    projects: 'blazorwasmapp.csproj'
    feedsToUse: 'select'
- task: DotNetCoreCLI@2
  displayName: Run dotnet build
  inputs:
    command: 'build'
    projects: 'blazorwasmapp.csproj'
    arguments: '--no-restore --configuration Release'
- task: DotNetCoreCLI@2
  displayName: Run dotnet publish
  inputs:
    command: 'publish'
    publishWebProjects: false
    projects: 'blazorwasmapp.csproj'
    arguments: '--no-build --configuration Release --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: false
- task: AzureStaticWebApp@0
  inputs:
    workingDirectory: '$(Build.ArtifactStagingDirectory)'
    app_location: 'blazorwasmapp/wwwroot'
    skip_app_build: true
    azure_static_web_apps_api_token: '$(deployment_token)'
```

今回作成したサンプルは GitHub で公開しておきますので、参考にしてみてください。
https://github.com/tsubakimoto/staticwebapp-azure-pipelines-blazorwasm