Azure Analytics 系サービスを選択式デプロイできるテンプレートを開発してみた。はまった点の備忘録

## はじめに

開発検証するリソースをセットで用意するのを効率化したくて、Azureの分析系サービスをワンクリックでデプロイできるテンプレートを開発してみました。

https://github.com/ryoma-nagata/AzureDataAnalyticsDevDeploy

Bicep完全に理解したと調子に乗って作ったわけですが、無事躓きポイントにぶつかったので、少々の備忘録を残します。

テンプレートはぜひ皆さん使ってください。

## パラメータによりリソースの作成有無を決定するテンプレートで起きたこと

### 問題：条件をつけてデプロイされない状態にしたリソースで参照が発生した

パラメータでリソースをデプロイするかどうか制御する、いわゆるコンディショナルデプロイ自体は非常に簡単です。

参考：https://docs.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/conditional-resource-deployment

通常、たとえばData Factory であれば、このように記述します。

```bicep
param location string = 'japaneast'

resource datafactory 'Microsoft.DataFactory/factories@2018-06-01' = {
  name: 'test-adf-qiita'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

```

これをある条件、たとえばisNeedDataFactoryのようなパラメータで制御すると以下のようになります。

```bicep
param location string = 'japaneast'
param isNeedDataFactory bool = false

resource datafactory 'Microsoft.DataFactory/factories@2018-06-01' = if(isNeedDataFactory == true){
  name: 'test-adf-qiita'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

```

単体ならこれでいいのですが、リソースのoutputを利用してデプロイするような構成だとどうでしょうか？
例でいえばData FactoryのIDを使ってデプロイするなどです。

よくやるのは、生成したdatafactoryに対してstorage accountの権限を割当てるようなケースです。

```bicep

param location string = 'japaneast'
param isNeedDataFactory bool = false
param lakeName string 

var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'

resource datafactory 'Microsoft.DataFactory/factories@2018-06-01' = if(isNeedDataFactory == true){
  name: 'test-adf-qiita'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

resource datalake 'Microsoft.Storage/storageAccounts@2021-09-01' existing = {
  name: lakeName
}

resource datafactoryTolandingRawLake 'Microsoft.Authorization/roleAssignments@2020-04-01-preview'  =if(isNeedDataFactory == true) {
  name: guid(lakeName,storageBlobDataContributorRoleId,'datafactoryToLake')
  scope: datalake
  properties: {
    roleDefinitionId:resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleId)
    principalId: datafactory.identity.principalId
    principalType: 'ServicePrincipal'
  }
}



```

このbicepはうまくいきません。
エラーとなり、原因はData factoryのリソースが見つからないという旨のメッセージが返されます。

> ResourceNotFound

挙動から判断したことですが、
なんらかのリソースを参照したデプロイを条件制御する場合、デプロイ条件をみたさない場合でも参照が発生します。



### 解決策

#### 参照が発生している箇所もif条件で切り替えるようにする

基本的な考え方は、リソースの参照を発生させないことです。

参照が発生するのは、principalIdの部分ですので以下のように変更します。

```bicep

principalId:(isNeedDataFactory == true)? datafactory.identity.principalId : ''

```

全文


``` bicep


param location string = 'japaneast'
param isNeedDataFactory bool = false
param lakeName string 

var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'

resource datafactory 'Microsoft.DataFactory/factories@2018-06-01' = if(isNeedDataFactory == true){
  name: 'test-adf-qiita'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

resource datalake 'Microsoft.Storage/storageAccounts@2021-09-01' existing = {
  name: lakeName
}

resource datafactoryTolandingRawLake 'Microsoft.Authorization/roleAssignments@2020-04-01-preview'  =if(isNeedDataFactory == true) {
  name: guid(lakeName,storageBlobDataContributorRoleId,'datafactoryToLake')
  scope: datalake
  properties: {
    roleDefinitionId:resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleId)
    principalId:  (isNeedDataFactory == true)? datafactory.identity.principalId : ''
    principalType: 'ServicePrincipal'
  }
}


```

これで回避できます。

上記は単純化していますが、今回作成したテンプレートはかなり複雑で、参照箇所を切り分けていくために当初考えていたよりもmoduleを分割する必要に迫られました。（この例だと伝わりにくいんですが、後処理的に設定しているものはかなり構成を再検討させられました）

今回の例で言うと、以下のように分割する感じです。

datafactory.bicep

``` bicep

param location string = 'japaneast'
param isNeedDataFactory bool = false
param lakeName string 


var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'

resource datafactory 'Microsoft.DataFactory/factories@2018-06-01' = if(isNeedDataFactory == true){
  name: 'test-adf-qiita'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

module rbac 'rbac.bicep' = if(isNeedDataFactory == true){
  name: 'rbac'
  params:
  {
      lakeName:lakeName
      principalId:(isNeedDataFactory == true)? datafactory.identity.principalId : ''
  }
}

```

rbac.bicep

``` bicep

param lakeName string 
param principalId string

resource datalake 'Microsoft.Storage/storageAccounts@2021-09-01' existing = {
  name: lakeName
}
　// ifが不要になる
resource datafactoryTolandingRawLake 'Microsoft.Authorization/roleAssignments@2020-04-01-preview'  = {
  name: guid(lakeName,storageBlobDataContributorRoleId,'datafactoryToLake')
  scope: datalake
  properties: {
    roleDefinitionId:resourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleId)
    principalId: principalId　// ifが不要になる
    principalType: 'ServicePrincipal'
  }
}

```


今回紹介したテンプレートはこの仕様をあとから対応したので依存関係が多少汚くなっちゃいましたが、
これから始める方は、こういった点を踏まえて、bicep のmodule分割を検討していただければと思います。