Preview リソースは ARM Template を見つけるところから気を付けよう。 
Bicepモジュール粒度はTerraform のモジュール粒度と同じコンセプトでよく機能する。 
param で object を使うときにはデフォルト値とaray of objectが使いにくい。 
Role のような GUID が name のリソースでは、逆引きできるように設計が必要。 
 
IaC で一番重要なのが、Resource Reference はどこを探せばいいのかの確認だ。
Bicep Resource は ARM Temaplte と相互に変換ができる。
ということで、Mirosoft は ARM Template の Reference に Bicep の定義も配置している。
Azure Resource Manager template reference - ARM template reference | Microsoft Docs 
 
型定義は、次の通り。改行に意味がある構文なので、慣れてない内は、ふとした変数定義でエラーになる。
Bicep functions - objects - Azure Resource Manager | Microsoft Docs 
 
Preview リソースと ARM Template  
Previewは、Azureと付き合っていくうえでめんどくさい側面の一つだ。
Azure は Preview じゃないと使いたい機能がない、というケースが多い。(それ自体はいいが、プレビューが長いのがAzureを使っていてつらいところ)
ということで Previewも扱えないか考えていこう。
Azure の ARM Template ページには「Previewを除くAzure リソース」は記載されているが、Previewリソースはここにない。
Preview リソースは、それぞれのPreview リソースの説明ページに存在する。
例えば、PostgreSQL Flexible Server は Preview なので、こっちを見ることになる。
クイック スタート:Azure DB for PostgresSQL フレキシブル サーバーを作成する - ARM テンプレート | Microsoft Docs 
 
Preview は、AWS だろうとどこだろうとAPIからドキュメントに至るまで何かと手薄だが、Azure も例外ではない。
このPreview のページには Database などの追加 ARM Templateの記載はもちろん、言及すらない、探す難易度が高い。そして Configuration に至っては存在しない。
幸いにして、ドキュメントになくてもVS Codeのbicep補完でリソースが出る。インテリセンスに頼ってエスパーしよう。
こういうところが Preview を使う上で本当に苦しいだろう。そしてPreview は長い、先が見えない不安が付きまとう。
細かいように思えるが、このドキュメントの一貫性の欠如はAzureを学ぶ上で、探すコストが著しく高く厳しいものがある。
Preview も同じARM template reference に置いて、定義をみるべき場所を減らせばいいと思うがしない理由もあるのだろう。
BicepのModule は、Terraform 同様にある程度の粒度で組むのがよさそう。
いわゆる 1 Resource で 1 Module というのはなるべく避けるべきだろう。(拡張性が事実上ない)
ただ、隠蔽するという意味では十分拡張性があってメンテコストが低いならあり。(resource を露出させたくないのもわかる)
ダメな例 
Subnet が array of object を受け付けるが、1subnet 固定 + vnet が同時に作成される前提になっている。
これでは利用者は 1 vnet に n subnet はできず、かならず vnet に 1 subnet が強制されるだろう。
@description ('Specifies the Azure location where the key vault should be created.' )
param  location  string  =resourceGroup ().location 
@description ('Tag information for vnet' )
param  tags  object  = {}
@description ('Virtual network name' )
param  virtualNetworkName  string 
@description ('Address prefix for virtual network' )
param  addressPrefix  string  = '10.0.0.0/8' 
@description ('Subnet name' )
param  subnetName  string 
@description ('Subnet prefix for virtual network' )
param  subnetPrefix  string  = '10.1.0.0/16' 
resource  vn  'Microsoft.Network/virtualNetworks@2020-06-01'  = {
  name : virtualNetworkName 
  location : location 
  tags : tags 
  properties : {
    addressSpace : {
      addressPrefixes : [
        addressPrefix 
      ]
    }
    subnets : [
      {
        name : subnetName 
        properties : {
          addressPrefix : subnetPrefix 
          privateEndpointNetworkPolicies : 'Disabled' 
        }
      }
    ]
  }
}
output  id  string  = vn .id 
output  name  string  = vn .name 
output  subnetIds  array  = [
  {
    id : vn .properties .subnets [0 ].id 
    name : vn .properties .subnets [0 ].name 
  }
]  
複数の AKS を構成する必要がないなら、ACR や ACR Role Assignment など、関連するリソースをまとめてしまうほうがいいだろう。
// パラメーター 
// リソース 
resource  vn  'Microsoft.Network/virtualNetworks@2020-06-01'  = {
}
resource  symbolicname  'Microsoft.ContainerRegistry/registries@2020-11-01-preview'  = {
}
// 他隠蔽できるリソース...  
// アウトぷっと 
output  id  string  = vn .id  
Terraform などを使っている人にとっては、Terraformモジュールと同じコンセプトで分離すればいい、といえば伝わるだろうか。
Parameterで活躍するのが型システムだ。
型が強く機能すれば、どのパラメーターに何をいれればいいのか、インテリセンスがドキュメントとして機能する。
Bicep の型定義システム自体は決して強くない。だが、VS Code のLanguage Server が強力に機能しているので、インテリセンスだけを見ると Terraform よりも書きやすい。
Data types in Bicep - Azure Resource Manager | Microsoft Docs 
 
型を指定すれば、パラメーターを渡すとき、使うときに型チェックされて入力している値の型と合致しているか見てくれる。
terraform と同程度には扱えるし、便利。
param  strParam  string 
param  enable  bool  
また、attirbute で @allowed などをparamの上の行の書けば入力を enum 値で制限もできて便利だ。
@allowed ([
  'apple' 
  'orange' 
])
param  fruit  string   
パスワードのようなセキュアな値は、@secured() を付ければSecureString として扱われて Deploy History などに乗らないのでこれも便利。
@secure()
param password string
 
Bicep のobject型は、型宣言時にプロパティを宣言できないため使いにくいという印象がぬぐえない。
// 宣言時にデフォルト値をもってプロパティが決まる 
param  foo  object  = {
    str_prop  = '' 
    num_prop  = 111 
    bool_prop  = true 
    array_prop  = []
} 
なぜ、型宣言時にプロパティを宣言できないのが使いにくいのだろうか。
IaC で避けられるなら避けたほうがいいのは、デフォルト値の設定だ。
デフォルト値が、オフィシャルのARM Template の bicep Resource のような本体ならいいのだが、Module として提供する場合はデフォルト値を入れた/入れてないで事故が起こりやすい。
そのため、基本的にパラメーターで与えたいものはデフォルト値なしで、型宣言だけして与えるのがよいと、私が見てきた多くの現場ではプラクティスとして得ている。
例えばterraform では、変数の型宣言は次のようにデフォルト値なしで行える。
variable  "foo"  {
  type  =  set (object ({
    str_prop    =  string 
    num_prop =  number 
    list_prop   =  list (string )
    set_prop   =  set (string )
    map_prop =  map (string )
  })) 
bicep も、object型宣言 時にプロパティと型を指定できれば事故を防げてうれしいのだが、できないので諦めよう。
同じことは array 型にも言えるが、string や int などの単一の型なら推論が効くので何も問題がない。
だが、object の array となると完全に無力だ。parameter に渡すとき、parameterを使うときの両方でインテリセンスは沈黙する。
そもそもの型宣言が array でしかないので無力としか言えない、ここからプロパティを推論できるようになるといいのだが。
param  foo  array  = [
  {
    str_prop  = '' 
    num_prop  = 111 
    bool_prop  = true 
    array_prop  = []
  } 
] 
terraform の list(map(string)) 型のインテリセンスの利かなさと同じといえばイメージしやすいだろうか。
bicep は、実行時に2つの方法でパラメーターを渡せる。
cli 引数 
jsonファイル参照 
 
cli 引数は -p key=value で指定できるので使いやすくはじめのうちはこれが多い。
az deployment group create --resource-group dev-foo -f foo.bicep -p key=value -p key2=value2 --mode Complete  
ただ、実際にCIで回し始めると dev や stg など、決まった環境に決まった実行を毎度行うことが多くなる。
ということで、いちいち引数設定せず json  にしておいて実行引数はいつも同じになっていくだろう。
az deployment group create --resource-group dev-foo -f foo.bicep -p @param.json --mode Complete  
json parameter がちょっと使いにくいのが、bicep で指定していない parameter が json に定義されていると引数が渡せずエラーが出ることだ。
設定ファイルを共通にして、いくつかの bicep ファイルに分ける (当然bicepごとにparamはそれぞれ違う)、という使い方には向いていないのでなんとももどかしいものがある。
az deployment group create --resource-group dev-foo -f foo.bicep -p @param.json --mode Complete
#  foo とbar で同じ param じゃないとパラメーター渡しでエラー 
az deployment group create --resource-group dev-bar -f foo.bicep -p @param.json --mode Complete  
諦めて、それぞれの bicepごとにparam を用意することになったが微妙。
existing は、いわゆる terraform の data リソースのように、既存のリソースからリソース参照を拾ってくる使い方のために用意されている。
Referencing existing resources 
 
たとえば、次のようなstorage account リソースを拾ってくる書き方ができる。
resource  stg  'Microsoft.Storage/storageAccounts@2019-06-01'  existing  = {
  name : 'myacc' 
} 
では、subnet のように、他のリソース(subnetなら vnet ) の中にあるリソースはどうやってとってくるかというと、vnet を拾ってから subnet を拾うのがいいだろう。
例えば次のようにする。
resource  vnet  'Microsoft.Network/virtualNetworks@2021-02-01'  existing  = {
  name : 'vnet-name' 
}
resource  subnet  'Microsoft.Network/virtualNetworks/subnets@2021-02-01'  existing  = {
  name : '${vnet .name }/my-subnet' 
} 
この existing 処理の問題点は、本当にそのリソースが取れたかの確証が取れないことだ。
通常 terraform や pulumi では、data resource で対象のリソースの取得に失敗した場合エラーで中断する。
だが、bicep では中断処理が行われない。
たとえば、先ほどの vnet を name ではなく id 参照にするとどうなるだろう。
resource  vnet  'Microsoft.Network/virtualNetworks@2021-02-01'  existing  = {
  name : 'vnet-name' 
}
resource  subnet  'Microsoft.Network/virtualNetworks/subnets@2021-02-01'  existing  = {
  name : '${vnet .id }/my-subnet'  // vnet.name から vnet.id 
} 
結果は、subnet が取れない、だ。それにも関わらずARM Template のデプロイ時にここはパスされて、後続の処理では「取れてないsubnet」を渡そうとする。結果、デプロイ自体はは、subnetを使うリソースで作成が失敗してエラーになる。
エラーメッセージもリソースが作れなかったことを示すのみで、それが subnet が取れなかったことには連想しにくい。
本来は、原因であるsubnet の取得で失敗してエラーになってほしいのは言うまでもない。
existing は、既存のリソースをとってくるが、とってきたことを保証しない。
これはIaC としては厄介な挙動で、what-if のような 実行前の確認で検知できないことを示している。
Terraform では data source を使うことで確証を取れるのだが、Bicep では実行前に az コマンドなどで取得してパラメーターに渡すぐらいしか確証とれなさそうだ。
なお、こういった subnet -> vnet という依存関係があるリソースは、id 上で {parent_id}/subnets/{subnet_name} のような resource id ルールが一般に存在するため、subnet を existing で拾う必要がない。
existing の現状の挙動では、無理して使う理由が乏しいので回避できるならするといいだろう。
Role には、Build-in Role と Custom Roleが存在する。
Azure のIAMはリソースごとに存在するので、RBAもリソースごとに他のリソースやRole と関連づけることになる。
つまり、role assignment は、リソースごとに行う。
参考: AWS の場合、IAM Role でリソースとアクションをポリシーとして集権して、IAM Role Arn をリソースに割り振る。
 
Role の特徴は、resourceIdの名前部分が GUID であることだ。
コマンドなら az role definition list --name 'ROLE_NAME'  | jq -r .[].id のようにすることでRole名さえわかっていれば Role Idを取得できる。
だが bicepでリソースをとってくるときは、Microsoft.Authorization/roleDefinitions リソースで existing 経由で取得しようと思っても、subscriptionResourceId 関数で取得しようと思っても、GUIDがわからないと使えないことに気づくだろう。
// resourceSubscription関数で 
subscriptionResourceId ('Microsoft.Authorization/roleDefinitions' , 'ここに入れるGUIDをどう導き出すか' )
// あるいは existing 使うなら 
resource  aksAcrPermissions  'Microsoft.Authorization/roleDefinitions@2018-01-01-preview'  existing  = {
  name : 'ここに入れるGUIDをどう導き出すか' 
} 
Role がGUIDであるため名前から推測できない。
ということで、Built-In Role、Custom Role それぞれで既存Roleを参照するときに工夫が必要となる。
Azure が提供している組み込みRole は、全アカウントで Role Name となる GUID が固定である。
一覧: Azure built-in roles - Azure RBAC | Microsoft Docs 
 
固定値なので何も考えずに GUID を必要に応じて渡すか、Role Name から GUID を返すだけのModuleを用意すればいいだろう。
現実的に考えると、bicepのモジュールは関数的に使うには無駄にしんどいので、GUID をそのまま渡すのがいいだろう。(terraform や Pulumiを考えると、こういうAzureで決定しているものの取得はbicep が組み込み関数で用意するべきだと思う)
例えば、AKS Clusterから ACR のイメージを取得する Role Assignment を与えるRole Assignmentを行うことを考えてみよう。
ACR からの Pull権限は、Build-in Role AcrPull で提供されており、GUID は 7f951dda-4ed3-4680-a7ca-43fe172d538d とわかっているので次のように書くことになるだろう。
resource  aks  'Microsoft.ContainerService/managedClusters@2021-03-01'  = {
  // プロパティ 
}
resource  acr  'Microsoft.ContainerRegistry/registries@2020-11-01-preview'  = {
  // プロパティ 
}
resource  aksAcrPermissions  'Microsoft.Authorization/roleAssignments@2020-04-01-preview'  = {
  name : guid (aks .name )
  scope : acr 
  properties : {
    roleDefinitionId : subscriptionResourceId ('Microsoft.Authorization/roleDefinitions' , '7f951dda-4ed3-4680-a7ca-43fe172d538d' )
    principalId : aks .identity .principalId 
  }
} 
Custom Role を定義した場合、その Role が同じModuleやリソースから参照できるならそれを使えばいい。
そうでなく、先ほどの Build-in Role のように既存の取得をしたい場合、Role作成時 の name 時点で工夫するしかない。
RoleDefinitions の name は、GUID だ。このGUID に bicep の Guid関数を利用し、引数に roleName を指定すればいい。
こうすれば、参照する側は roleName がわかっていれば、Guid関数で逆引きができる。
コードで見てみよう。
ロールを作成するときに工夫するのがすべてだ。
var  role_name  = 'my_awesome_role' 
resource  hoge  'Microsoft.Authorization/roleDefinitions@2018-01-01-preview'  = {
  name : guid (role_name ) // ここで role_name を知っていればguid が算出できるようにする。 
  properties : {
    roleName : role_name 
    // ほかのプロパティ 
  }
} 
あとは、resource が直接参照できなくても、次の方法で導き出すことができる。
// subscriptionResourceId 関数で取得 
subscriptionResourceId ('Microsoft.Authorization/roleDefinitions' , guid ('my_awesome_role' ))
// existing で取得 
resource  aksAcrPermissions  'Microsoft.Authorization/roleDefinitions@2018-01-01-preview'  existing  = {
  name : guid ('my_awesome_role' )
} 
いくつか書いていてつらいのでサポートが欲しい機能。