tl;dr
例
※コードは例であり、実際の動作確認はしていません
例として、SQL Database for SQL Serverを作り、そこに複数のIP制限をかけたい場合を考える。
ベースはこれ。
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
},
"variables": {
"sqlServerName": "mysqlserver",
"sqlServerAdminName": "superadmin",
"sqlServerAdminPassword": "5ecRet!"
},
"resources": [
{
"comments": "SQL Database for SQL Server",
"type": "Microsoft.Sql/servers",
"name": "[variables('sqlServerName')]",
"apiVersion": "2015-05-01-preview",
"location": "[resourceGroup().location]",
"properties": {
"administratorLogin": "[variables('sqlServerAdminName')]",
"administratorLoginPassword": "[variables('sqlServerAdminPassword')]",
},
"resources": [
{
"type": "firewallRules",
"apiVersion": "2014-04-01-preview",
"dependsOn": [
"[concat('Microsoft.Sql/servers/', variables('sqlServerName'))]"
],
"name": "AllowAllWindowsAzureIps",
"properties": {
"endIpAddress": "0.0.0.0",
"startIpAddress": "0.0.0.0"
}
}
]
}
]
}
ここで、複数のグローバルIPを与えて制限を行いたいと考え、以下のような配列を用意してVariablesを設定する。
"sqlServerAllowIps": ["1.1.1.1", "2.2.2.2"]
この時、書き方としては以下のように「配列の数だけ繰り返してリソースを用意すればよい」と思いついた。が、これはうまくいかない。
"resources": [
{
"comments": "SQL Database for SQL Server",
"type": "Microsoft.Sql/servers",
"name": "[variables('sqlServerName')]",
"apiVersion": "2015-05-01-preview",
"location": "[resourceGroup().location]",
"properties": {
"administratorLogin": "[variables('sqlServerAdminName')]",
"administratorLoginPassword": "[variables('sqlServerAdminPassword')]",
},
"resources": [
{
"type": "firewallRules",
"apiVersion": "2014-04-01-preview",
"dependsOn": [
"[concat('Microsoft.Sql/servers/', variables('sqlServerName'))]"
],
"name": "AllowAllWindowsAzureIps",
"properties": {
"startIpAddress": "0.0.0.0",
"endIpAddress": "0.0.0.0"
}
- }
+ },
+ {
+ "type": "firewallRules",
+ "apiVersion": "2014-04-01-preview",
+ "dependsOn": [
+ "[concat('Microsoft.Sql/servers/', variables('sqlServerName'))]"
+ ],
+ "name": "[concat('AllowIp-', copyIndex())]",
+ "properties": {
+ "startIpAddress": "[variables('sqlServerAllowIps')[copyIndex()]]",
+ "endIpAddress": "[variables('sqlServerAllowIps')[copyIndex()]]"
+ },
+ "copy": {
+ "name": "dummy",
+ "count": "[variables('sqlServerAllowIps')]"
+ }
+ }
]
}
]
これを実行すると以下のようなエラーになる。
Deployment template validation failed: 'The template resource '[concat('AllowIp-', copyIndex())]' at line '1' column '123' is not valid. Copying nested resources is not supported. Please see https://aka.ms/arm-copy/#looping-on-a-nested-resource for usage details.'.
ざっくりいえば、ネストしたリソースに対してcopyを使うことはできない、という意味。
エラー文に含まれているURLを見ろ、ということだが、これは少しわかりにくいと感じたので今回補足する。
先に成功例を貼ると、以下のように修正する。ネストさせない形で書けば良い。
"resources": [
{
"comments": "SQL Database for SQL Server",
"type": "Microsoft.Sql/servers",
"name": "[variables('sqlServerName')]",
"apiVersion": "2015-05-01-preview",
"location": "[resourceGroup().location]",
"properties": {
"administratorLogin": "[variables('sqlServerAdminName')]",
"administratorLoginPassword": "[variables('sqlServerAdminPassword')]",
},
"resources": [
{
"type": "firewallRules",
"apiVersion": "2014-04-01-preview",
"dependsOn": [
"[concat('Microsoft.Sql/servers/', variables('sqlServerName'))]"
],
"name": "AllowAllWindowsAzureIps",
"properties": {
"startIpAddress": "0.0.0.0",
"endIpAddress": "0.0.0.0"
}
}
]
- }
+ },
+ {
+ "type": "Microsoft.Sql/servers/firewallRules",
+ "apiVersion": "2014-04-01-preview",
+ "dependsOn": [
+ "[concat('Microsoft.Sql/servers/', variables('sqlServerName'))]"
+ ],
+ "name": "[concat(variables('sqlServerName'), '/', 'AllowIp', '-', copyIndex())]",
+ "properties": {
+ "startIpAddress": "[variables('sqlServerAllowIps')[copyIndex()]]",
+ "endIpAddress": "[variables('sqlServerAllowIps')[copyIndex()]]"
+ },
+ "copy": {
+ "name": "dummy",
+ "count": "[variables('sqlServerAllowIps')]"
+ }
+ }
+ ]
要点は以下
- ネストにならないように変更する
-
type
を変更する指定する(ネストではなくなったので。) -
name
は"引数1/引数2"
というイメージで設定する必要がある。- firewallRulesの例でいうと、
name
には"{serverName}/{firewallRuleName}"
となるようにする必要がある。 -
{serverName}
は実際に存在するSQL Serverのリソースの名前を指定する。variables('sqlServerName')
になる。 -
{firewallRuleName}
はユニークな値を選ぶ必要がある。(ここはユニークになれば自由な名前でよい)
- firewallRulesの例でいうと、
分かりにくい解説
少し話はズレるが、Rest APIのURLを示すと以下のような形となっている。
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/firewallRules/{firewallRuleName}?api-version=2014-04-01
(引用元: https://docs.microsoft.com/en-us/rest/api/sql/firewallrules/createorupdate)
この時、providers
以降の文字列を見てほしい。
Microsoft.Sql/servers/{serverName}/firewallRules/{firewallRuleName}
この形はリソーステンプレートのtype
の部分に似ている。しかし、間に{serverName}
を書く形となっており、少し違う。type
にはこのようには書けない。
ARMTemplateではname
に必要なパラメータを書く決まりとなっているようだ。なので、自由な名前を付けるだけではなく、このRestAPIの可変値の部分を満たすように設定する必要がある。
書き方も決まっており、可変値を順番に/
で区切りながら設定しなければならない。今回の場合は"{serverName}/{firewallRuleName}"
となる。
なので"[concat(variables('sqlServerName'), '/', 'AllowIp-', copyIndex())]"
のように指定し、作成した際のserverNameを与えつつ、ループ毎にユニークなfirewallRuleNameを与える形にする必要があった。
ARM Template辛い。