Azure DevOps PipelineからBicep deploy

今更ながらBicepの勉強。ただのMSLearnのまとめです。

https://learn.microsoft.com/ja-jp/training/paths/bicep-azure-pipelines/

Azure DevOps Pipeline自体の説明が多くなったが、復習ということで。

Biceps基礎はこちら。

https://qiita.com/smr1/items/47e509f62b131cbccb64

# 準備
## Service ConnectionにAzureへの接続情報を設定
Azure PipelineからAzureにアクセスするためにService Connectionを利用する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/0e4ee0b2-e34b-b542-6d51-52d5d0e518d4.png)
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/7039fb64-c072-a004-f539-873209e23350.png)
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/65bfd53c-3a94-baf2-ce08-b3e6888c1854.png)

## サービスプリンシパル（マニュアル）
サービスプリンシパルを手動で作る場合の手順。
Automaticの場合はいかが自動で行われる。

Bicepファイルを手動でDeployするときはその人間の認証情報で実行される。PipelineからAzureを操作するのにはサービスプリンシパルを利用する。原則Pipeline実行にはマネージドIDは不可。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/cc0f2f64-7229-00a6-55ce-2432f6c47263.png)

SPを作るときに同時に権限付与する。パイプラインで実行する処理に合わせてSPに権限をRBACで付与する。推奨はResource Groupの共同作成者Contributorロール。
```
az role assignment create \
  --assignee APPLICATION_ID \
  --role Contributor \
  --scope RESOURCE_GROUP_ID \
  --description "The deployment pipeline for the company's website needs to be able to create resources within the resource group."

// Response
{
  "appId": "{appid}",
  "displayName": "ToyWebsitePipeline",
  "password": "{pwd}",
  "tenant": "{tenant}"
}
```

コマンドの結果パスワードが返ってきている。
Entra IDでApp Registrationが追加されているのを確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/3157556c-d100-1355-28e5-c2ebabb0a321.png)

## Bicep file作成
ちょっと長いけど、作るリソースは3つだけ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/37becb94-7244-579a-6a7b-174de211f9e8.png)

※Bicepは全部で共通なので最後に載せる。

## Bicep DeployのためのPipeline yaml作成
AzureResourceManagerTemplateDeployment@3タスクを使う。
```
trigger: none

pool:
  vmImage: ubuntu-latest

variables:
  - name: deploymentDefaultLocation
    value: westus3

jobs:
  - job:
    steps:
      - task: AzureResourceManagerTemplateDeployment@3
        inputs:
          connectedServiceName: $(ServiceConnectionName)
          deploymentName: $(Build.BuildNumber)
          location: $(deploymentDefaultLocation)
          resourceGroupName: $(ResourceGroupName)
          csmFile: deploy/main.bicep
          overrideParameters: >
            -environmentType $(EnvironmentType)
            -deployToyManualsStorageAccount $(DeployToyManualsStorageAccount)
```

## Variableを設定
パラメータの持ち方・渡し方はいくつかある。
- 渡さないでbicepの中の既定値を利用する
　
- Pipelineのyamlからデプロイタスクを呼ぶときに渡す
　
- DevOpsのエディタからパイプライン専用のVariablesで設定する
MSLearnではこのやり方で。以下の右上のVariablesから。
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/8df1e5d6-59d5-cf14-8e38-8a07aa12752a.png)
シークレットに設定や実行時にオーバーライドが選択可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/039435c3-adfb-bf78-a50f-5a632f6a4bce.png)
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/e5d148f0-3541-a1b8-09f7-0e78f221d60f.png)
定義した変数はさっきのyamlにあるように$で利用可能。
　
- Variable Groupという変数を共有できる機能もある
パイプライン専用の変数はここには表示されない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/f649a593-9979-8a37-f79a-94c608705e81.png)

## 実行
Runを押すとVariablesが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/a0ab4fb3-86cc-8295-155a-31d44d50305d.png)
　
上書き可能にしたものはここで変更可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/108240ac-f753-1f6d-74f4-534a0793bc26.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/7ded68d7-6c48-6d02-7e44-1896def2ba21.png)

デプロイ履歴を確認したところ、日付になってた。deploymentName: $(Build.BuildNumber)の引数によるものかと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/a728b17e-25c4-b7f6-f61e-a1fbc251d454.png)

# Pipelineトリガー
```
// ブランチやパスフィルター
trigger:
  branches:
    include:
    - main
  paths:
    exclude:
    - docs
    include:
    - deploy

// もしくはTriggerの代わり or 追加でcron式で自動実行
schedules:
- cron: "0 0 * * *"
  displayName: Daily environment restore
  branches:
    include:
    - main

// 短時間で複数の更新があった時などは平行して実行される
// IaCのデプロイパイプラインは順次実行の方がよい、batch: true推奨
trigger:
  batch: true
  branches:
    include:
    - main
```

# Stageを使う
基本的に上記までだけでも動くが、IaCの場合Validationやステージを利用して柔軟に設定する。これらのジョブはステージごとに新しいエージェントで動く。

https://learn.microsoft.com/ja-jp/training/modules/test-bicep-code-using-azure-pipelines/2-understand-pipeline-stages

※上記の記事を丸パクリしているだけなので、直接読んだ方がいい。。

ConditionやDependsOnについても色々細かくカスタマイズ可能。

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/conditions?view=azure-devops&tabs=yaml%2Cstages

## Stageのパターン
MSLearnで紹介されている代表的なものは以下。

- 順次実行
　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/15e539f5-2580-d125-b8ce-b54b75e93fdf.png)

```
// 何もしない普通にやると順次になる
stages:

- stage: Test
  jobs:
  - job: Test

- stage: DeployUS
  jobs: 
  - job: DeployUS

- stage: DeployEurope
  jobs: 
  - job: DeployEurope
```

- 依存関係あり実行（並列という意味ではない）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/8ec02e32-9a5e-e207-a53c-c3d30ca8b638.png)
```
// dependsOnで前のジョブに依存するように指定（正直イマイチ必要性がわからない。。）
stages:

- stage: Test
  jobs:
  - job: Test

- stage: DeployUS
  dependsOn: Test
  jobs: 
  - job: DeployUS

- stage: DeployEurope
  dependsOn: Test
  jobs: 
  - job: DeployEurope
```

- 条件指定実行

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/26001d6c-62b0-5b5b-3c06-d994a7a65d9d.png)

 ```
 // 前のステップが失敗したらRollbackを実行する
 stages:

- stage: Test
  jobs:
  - job: Test

- stage: Deploy
  dependsOn: Test
  jobs: 
  - job: Deploy

- stage: Rollback
  condition: failed('Deploy')
  jobs: 
  - job: Rollback
 ```

## IaCでのValidation
こんな感じで順を追って実行していく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/5e8d4a3e-591a-848f-82b2-1511ee948250.png)

### Linting
Validation、構文チェック。
```
// 未使用変数、かつsecure()で、かつ既定値ありのパラメータを定義
@secure()
param someSecretVal string = 'testSecret!'

// Linting = ARM TemplateへTranspileしてみる
az bicep build --file main.bicep

// 警告が表示される
C:\work\Bicep\Pipeline\deploy> az bicep build --file main.bicep
C:\work\Bicep\Pipeline\deploy\main.bicep(2,7) : Warning no-unused-params: Parameter "someSecretVal" is declared but never used. [https://aka.ms/bicep/linter/no-unused-params]
C:\work\Bicep\Pipeline\deploy\main.bicep(2,28) : Warning secure-parameter-default: Secure parameters should not have hardcoded defaults (except for empty or newGuid()). [https://aka.ms/bicep/linter/secure-parameter-default]
```

bicepconfig.jsonで警告をエラーにするとかエラーレベルの設定が可能。（これはVisual Studio Codeで書いているときにも警告として表示されるので、その時に対応してくれればよい。Mustではないかもしれないが。。）

https://techblog.ap-com.co.jp/entry/2021/06/12/154650

その他として、ここでPSRuleを実行するのもいいかも（個々人の環境作るのが大変な場合？）

https://zenn.dev/openjny/articles/dbf98272d2317b

### Preflight検証
既に使用されているリソース名やリージョンによってデプロイ可否とかをデプロイ前にチェックする。

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/azure-resource-manager-template-deployment-v3?view=azure-pipelines

DeploymentModeでValidationにする。
- Incremental（既定）
- Complete（既存削除＋完全新規）
- Validation（検証）

なので、以下のコマンドでは実際のデプロイはされない。
```
- stage: Validate
  jobs:
  - job: Validate
    steps:
      - task: AzureResourceManagerTemplateDeployment@3
        inputs:
          connectedServiceName: 'MyServiceConnection'
          location: $(deploymentDefaultLocation)
          deploymentMode: Validation    // DeploymentModeをValidationにする！
          resourceGroupName: $(ResourceGroupName)
          csmFile: deploy/main.bicep
```

### Stageを踏まえたDeploy
上記2点を踏まえると、以下のようなYamlができる。
```
# triggerやpoolとかvariablesとか省略

stages:
  - stage: Lint
    jobs:
      - job: LintCode
        displayName: Lint code
        // 警告をエラーにレベルを変更しないとこのままだと警告だけで通る
        steps:
          - script: |
              az bicep build --file deploy/main.bicep
            name: LintBicepCode
            displayName: Run Bicep linter

  - stage: Validate
    jobs:
      - job: ValidateBicepCode
        displayName: Validate Bicep code
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            name: RunPreflightValidation
            displayName: Run preflight validation
            inputs:
              connectedServiceName: $(ServiceConnectionName)
              location: $(deploymentDefaultLocation)
              deploymentMode: Validation
              resourceGroupName: $(ResourceGroupName)
              csmFile: deploy/main.bicep
              overrideParameters: >
                -environmentType $(EnvironmentType)

  - stage: Deploy
    jobs:
      - job: Deploy
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            name: Deploy
            displayName: Deploy to Azure
            inputs:
              connectedServiceName: $(ServiceConnectionName)
              deploymentName: $(Build.BuildNumber)
              location: $(DeploymentDefaultLocation)
              resourceGroupName: $(ResourceGroupName)
              csmFile: deploy/main.bicep
              overrideParameters: >
                -environmentType $(EnvironmentType)
```

前のStageが失敗しているのでDeployまでいかない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/e7fe55e9-bd7f-a159-525e-72286fade442.png)

# What Ifして人が承認する
上記でもそこそこいい気がするが、What Ifをして、その結果を元に本当にDeployに問題がなさそうか人の承認を取り入れたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/d68fcb0c-7add-2bcc-3b88-bd092f05ffd2.png)

Environmentという機能でDeploy Jobという特殊なJobを作る必要がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/c8412795-f2b9-8274-6888-1f9b807a9b59.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/76506354-e4ef-4b97-d7a6-49f41e60243c.png)

Approvalsを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/6525fb3c-70cd-e712-d9cb-35ac7c9d1551.png)

承認者を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/fc784332-c9da-9157-75e4-770c928edc7d.png)

```
// Triggerや検証部分等は省略
// 承認の設定とかをYamlファイルで定義できないのが残念に感じる

- stage: Preview
  jobs:
  - job: PreviewAzureChanges
    displayName: Preview Azure changes
    steps:
      - task: AzureCLI@2
        name: RunWhatIf
        displayName: Run what-if
        inputs:
          azureSubscription: $(ServiceConnectionName)
          scriptType: 'bash'
          scriptLocation: 'inlineScript'
          // What Ifの実行
          inlineScript: |
            az deployment group what-if \
              --resource-group $(ResourceGroupName) \
              --template-file deploy/main.bicep \
              --parameters environmentType=$(EnvironmentType)

- stage: Deploy
  jobs:
  - deployment: DeployWebsite
    displayName: Deploy website
    environment: Website
    strategy:
      runOnce:
        deploy:
          steps:
            // Environementを利用する場合、新しくファイルをチェックアウト（ダウンロード）し、
            // その環境にデプロイする
            - checkout: self

            - task: AzureResourceManagerTemplateDeployment@3
              name: DeployBicepFile
              displayName: Deploy Bicep file
              inputs:
                connectedServiceName: $(ServiceConnectionName)
                deploymentName: $(Build.BuildNumber)
                location: $(deploymentDefaultLocation)
                resourceGroupName: $(ResourceGroupName)
                csmFile: deploy/main.bicep
                overrideParameters: >
                  -environmentType $(EnvironmentType)
```

メールが届いた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/204fb53c-529c-f894-6a33-be51269963f5.png)

Review approvalを押すと実行中の画面になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/78af6a17-90da-ed7e-222f-909759ef0ee2.png)

PreviewのJobを確認するとWhat Ifの結果が見れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/a0ba9847-6aa0-08fc-6cd2-59380386f213.png)

OKであればReviewからApprove。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/da02959c-d850-b2dc-1a5d-c9b190b7778c.png)

# Deploy結果をテストする
もうそろそろお腹一杯になってきた。。。

いわゆるスモークテストの実行。ここではPowershellベースのPesterを使う。

https://qiita.com/nimzo6689/items/65948bbfcda1f775a91f

面白いけど複雑なのでリンクも載せておく。

https://learn.microsoft.com/ja-jp/training/modules/test-bicep-code-using-azure-pipelines/9-exercise-add-test-stage-pipeline?pivots=cli

## Pesterによるスモークテストの実行
以下のファイルを作成する。
テストでは前段階でDeployしたApp ServiceのHost名を受け取って、それに対して単純に疎通確認だけを行う。
### Pesterでテストケースを作成
```
param(
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $HostName
)

Describe 'Toy Website' {

    It 'Serves pages over HTTPS' {
      $request = [System.Net.WebRequest]::Create("https://$HostName/")
      $request.AllowAutoRedirect = $false
      $request.GetResponse().StatusCode |
        Should -Be 200 -Because "the website requires HTTPS"
    }
}
```

### bicepでouputを定義
bicep側でoutputとしてDeployしたApp ServiceのHostNameを出力していることを確認する。これを最終的にPesterのスクリプトに渡したい。
```
@description('The Azure region into which the resources should be deployed.')
param location string = resourceGroup().location

@description('The type of environment. This must be nonprod or prod.')
@allowed([
  'nonprod'
  'prod'
])
param environmentType string

@description('Indicates whether to deploy the storage account for toy manuals.')
param deployToyManualsStorageAccount bool = false

@description('A unique suffix to add to resource names that need to be globally unique.')
@maxLength(13)
param resourceNameSuffix string = uniqueString(resourceGroup().id)

var appServiceAppName = 'toy-website-${resourceNameSuffix}'
var appServicePlanName = 'toy-website-plan'
var toyManualsStorageAccountName = 'toyweb${resourceNameSuffix}'

// Define the SKUs for each component based on the environment type.
var environmentConfigurationMap = {
  nonprod: {
    appServicePlan: {
      sku: {
        name: 'F1'
        capacity: 1
      }
    }
    toyManualsStorageAccount: {
      sku: {
        name: 'Standard_LRS'
      }
    }
  }
  prod: {
    appServicePlan: {
      sku: {
        name: 'S1'
        capacity: 2
      }
    }
    toyManualsStorageAccount: {
      sku: {
        name: 'Standard_ZRS'
      }
    }
  }
}

var toyManualsStorageAccountConnectionString = deployToyManualsStorageAccount
  ? 'DefaultEndpointsProtocol=https;AccountName=${toyManualsStorageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${toyManualsStorageAccount.listKeys().keys[0].value}'
  : ''

resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: appServicePlanName
  location: location
  sku: environmentConfigurationMap[environmentType].appServicePlan.sku
}

resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
  name: appServiceAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      appSettings: [
        {
          name: 'ToyManualsStorageAccountConnectionString'
          value: toyManualsStorageAccountConnectionString
        }
      ]
    }
  }
}

resource toyManualsStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' =
  if (deployToyManualsStorageAccount) {
    name: toyManualsStorageAccountName
    location: location
    kind: 'StorageV2'
    sku: environmentConfigurationMap[environmentType].toyManualsStorageAccount.sku
  }

output appServiceAppHostName string = appServiceApp.properties.defaultHostName
```

```
// Outputはこんな感じになる
{
  "appServiceAppHostName": {
    "type": "String",
    "value": "toy-website-xunjewfivysf2.azurewebsites.net"
  }
}
```

### PipelineでDeployしたApp ServiceのHostNameを変数としてPesterのJobに渡す
変数の渡し方がかなりややこしい。
```
// 普通にPipelineの中でStage間に変数を受け渡すのならこれで済むのだが
$[ stageDependencies.{stage名}.{job/deployment名}.outputs['変数名'] ]

// task.setvariableにより動的にパイプラインの実行中に変数を設定している
// bicepの出力がjson形式のため、平文に変換してやる必要がある、jqというコマンドがそれをしている
- bash: |
    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
  name: SaveDeploymentOutputs
  displayName: Save deployment outputs into variables
  env:
    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
```

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#use-outputs-in-a-different-stage
```
// 詳しくは公式へ
https://learn.microsoft.com/ja-jp/training/modules/test-bicep-code-using-azure-pipelines/9-exercise-add-test-stage-pipeline?pivots=cli
trigger:
  batch: true
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  - name: deploymentDefaultLocation
    value: westus3

stages:
  - stage: Lint
    jobs:
      - job: LintCode
        displayName: Lint code
        steps:
          - script: |
              az bicep build --file deploy/main.bicep
            name: LintBicepCode
            displayName: Run Bicep linter

  - stage: Validate
    jobs:
      - job: ValidateBicepCode
        displayName: Validate Bicep code
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            name: RunPreflightValidation
            displayName: Run preflight validation
            inputs:
              connectedServiceName: $(ServiceConnectionName)
              location: $(deploymentDefaultLocation)
              deploymentMode: Validation
              resourceGroupName: $(ResourceGroupName)
              csmFile: deploy/main.bicep
              overrideParameters: >
                -environmentType $(EnvironmentType)

  - stage: Preview
    jobs:
      - job: PreviewAzureChanges
        displayName: Preview Azure changes
        steps:
          - task: AzureCLI@2
            name: RunWhatIf
            displayName: Run what-if
            inputs:
              azureSubscription: $(ServiceConnectionName)
              scriptType: "bash"
              scriptLocation: "inlineScript"
              inlineScript: |
                az deployment group what-if \
                  --resource-group $(ResourceGroupName) \
                  --template-file deploy/main.bicep \
                  --parameters environmentType=$(EnvironmentType)

  - stage: Deploy
    jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        environment: Website
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: $(ServiceConnectionName)
                    deploymentName: $(Build.BuildNumber)
                    location: $(deploymentDefaultLocation)
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                    deploymentOutputs: deploymentOutputs

                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)

  - stage: SmokeTest
    jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
        steps:
          - task: PowerShell@2
            name: RunSmokeTests
            displayName: Run smoke tests
            inputs:
              targetType: inline
              script: |
                $container = New-PesterContainer `
                  -Path 'deploy/Website.Tests.ps1' `
                  -Data @{ HostName = '$(appServiceAppHostName)' }
                Invoke-Pester `
                  -Container $container `
                  -CI

          - task: PublishTestResults@2
            name: PublishTestResults
            displayName: Publish test results
            condition: always()
            inputs:
              testResultsFormat: NUnit
              testResultsFiles: "testResults.xml"
```

## テスト結果の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/7ba249fb-49e4-0fab-12af-22b20b1d5a1c.png)

Testsタブで結果の確認。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/c58b4ae6-2f67-50f3-7e5e-defae631c512.png)



## テスト失敗時のRollback or Rollforward

az deployment group create --rollback-on-errorで直前の成功したDeploment、もしくは過去の特定のDeploymentを再実行するということができる。

が、deploymentmodeがcompleteでない限り、作成されたリソースは削除されないので、完全に戻すことは難しい。そのため、ダメだった個所を修正してDeployしなおすRollForward推奨。

https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/rollback-on-error#azure-cli




#### 最後に
Bicepの勉強もこれで一区切りにします。本当はPipelineの上級者向けやBicepの上級もあるけど、それはまたいつか。。

https://learn.microsoft.com/ja-jp/training/paths/advanced-bicep/