如何使用 ARM 模板副本有条件地为存储帐户提供 N 个角色分配?

How to conditionally provision N role assignments for storage accounts with ARM Template copy?

我正在尝试预配 N 个存储帐户、N 个角色分配(每个存储帐户 1 个)以授予对特定身份的访问权限,但仅有条件地部署角色分配。存储帐户和标识已经存在,它们的模板逻辑已经运行了一段时间。

如果我尝试部署以下模板片段,我会在“'[concat(parameters('BackupStorageAccountRoleAssignmentsDeployment') [0].AccountName, '/Microsoft.Authorization/', guid(parameters('BackupStorageAccountRoleAssignmentsDeployment')[0].Name))]'" 当角色分配的输入数组为空时.

我已经在使用技巧将长度为 0 的副本强制为长度 = 1,然后根据条件进行保护部署。我已经尝试使用大小为 1 的默认数组进行变体,将我的角色分配部分移动到嵌套模板中,并手动将我的循环展开为 4 个角色分配。无论如何,我遇到了同样的错误。这段代码有什么问题?

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "RoleAssignments": {
            "type": "array"
        },
        "IdentityName": {
            "type": "string"
        },
        "StorageAccounts": {
            "type": "array"
        }
    },
    "variables": {
        "IdentityResourceId": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('IdentityName'))]"
    },
    "resources": [
        { // Everything related to role assignments chokes completely, even if I unroll the loop
            "dependsOn": [
                "[variables('IdentityResourceId')]",
                "storageaccountcopy"
            ],
            "copy": {
                "name": "RoleAssignmentsCopy",
                "count": "[max(length(parameters('BackupStorageAccountRoleAssignmentsDeployment')), 1)]"
            },
            "condition": "[greater(length(parameters('RoleAssignments')), 0)]",
            "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
            "apiVersion": "2020-04-01-preview",
            "name": "[concat(parameters('RoleAssignments')[copyIndex()].AccountName, '/Microsoft.Authorization/', guid(parameters('RoleAssignments')[copyIndex()].Name))]",
            "properties": {
                "roleDefinitionId": "[parameters('RoleAssignments')[copyIndex()].RoleDefinitionId]",
                "principalId": "[if(and(empty(parameters('RoleAssignments')[copyIndex()].ResourceId), not(empty(parameters('RoleAssignments')[copyIndex()].PrincipalId))), 
                                parameters('RoleAssignments')[copyIndex()].PrincipalId, 
                                reference(parameters('RoleAssignments')[copyIndex()].ResourceId, '2018-11-30').PrincipalId)]",
                "scope": "[parameters('RoleAssignments')[copyIndex()].Scope]",
                "principalType": "[parameters('RoleAssignments')[copyIndex()].PrincipalType]"
            }
        },
        {   // Everthing related to storage account provisioning works fine, this was existing code
            "condition": "[and(greater(length(parameters('StorageAccounts')), 0), not(parameters('SkipStorageAccountProvisioning')), equals(parameters('StorageAccountProvisioningDefault'), 'true'))]",
            "copy": {
                "name": "storageaccountcopy",
                "count": "[length(parameters('StorageAccounts'))]"
            },
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2019-06-01",
            "name": "[parameters('StorageAccounts')[copyIndex()].AccountName]",
            "location": "[resourceGroup().location]",
            "sku": {
                "name": "[if(equals(parameters('StorageAccounts')[copyIndex()].AccountTypeOverride, parameters('StorageAccountTypeOverrideDefault')), parameters('StorageAccounts')[copyIndex()].AccountType, parameters('StorageAccounts')[copyIndex()].AccountTypeOverride)]",
                "tier": "Standard"
            },
            "kind": "[parameters('StorageAccounts')[copyIndex()].AccountKind]",
            "properties": {
                "networkAcls": {
                    "bypass": "AzureServices",
                    "virtualNetworkRules": [],
                    "ipRules": [],
                    "defaultAction": "Allow"
                },
                "supportsHttpsTrafficOnly": true,
                "encryption": {
                    "services": {
                        "file": {
                            "keyType": "Account",
                            "enabled": true
                        },
                        "blob": {
                            "keyType": "Account",
                            "enabled": true
                        }
                    },
                    "keySource": "Microsoft.Storage"
                }
            }
        }
    ]
}

编辑:

根据反馈,我尝试将代码段移动到嵌套模板中。失败并出现完全相同的错误,The template resource '[concat(parameters('RoleAssignments')[copyIndex()].AccountName, '/Microsoft.Authorization/', guid(parameters('BackupStorageAccountRoleAssignments')[copyIndex()].Name))]' at line '1' and column '837' is not valid: The language expression property array index '0' is out of bounds..

{
      "dependsOn": [
        "[variables('IdentityResourceId')]"
      ],
      "type": "Microsoft.Resources/deployments",
      "apiVersion": "2020-10-01",
      "name": "RoleAssignmentsDeployment",
      "properties": {
        "mode": "Incremental",
        "expressionEvaluationOptions": {
          "scope": "inner"
        },
        "parameters": {
          "RoleAssignments": {
            "value": "[parameters('RoleAssignmentsDeployment')]"
          }
        },
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {
            "RoleAssignments": {
              "type": "array"
            }
          },
          "resources": [
            {
              "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts', parameters('RoleAssignments')[copyIndex()].AccountName)]"
              ],
              "copy": {
                "name": "RoleAssignmentsDeploymentCopy",
                "count": "[max(length(parameters('RoleAssignments')), 1)]"
              },
              "condition": "[greater(length(parameters('RoleAssignments')), 0)]",
              "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
              "apiVersion": "2020-10-01",
              "name": "[concat(parameters('RoleAssignments')[copyIndex()].AccountName, '/Microsoft.Authorization/', guid(parameters('RoleAssignments')[copyIndex()].Name))]",
              "properties": {
                "scope": "[parameters('RoleAssignments')[copyIndex()].Scope]",
                "principalType": "[parameters('RoleAssignments')[copyIndex()].PrincipalType]",
                "roleDefinitionId": "[parameters('RoleAssignments')[copyIndex()].RoleDefinitionId]",

                "principalId": "[if(and(empty(parameters('RoleAssignments')[copyIndex()].ResourceId), not(empty(parameters('RoleAssignments')[copyIndex()].PrincipalId))), 
                                    parameters('RoleAssignments')[copyIndex()].PrincipalId, 
                                    reference(parameters('RoleAssignments')[copyIndex()].ResourceId, '2020-10-01').PrincipalId)]"
              }
            }
          ]
        }
      }
    }

零长度数组上的复制循环问题是已知的,Microsoft 团队已在其路线图上修复它。

目前的解决方案是将循环移动到嵌套部署中,将数组作为参数传递,并在嵌套部署条件下检查数组是否不为空。虽然我不确定部署是否必须在内部范围内。当然,它将变量范围设置为内部。

所以有几种方法可以解决这个问题。我很难让 Miq 的建议按照我想要的方式工作。不过,我确实找到了另一种解决方法;在模板中使用一个变量来确保输入数组总是被填充

备注:

  • 参数上的常规“默认值”属性 将不起作用,如果传递空数组,则不会应用默认值。您需要使用一些 if 块来计算单元素数组作为无操作部署的默认值

  • ARM 在“编译”时扩展模板。因此,您会根据模板的替换值而不是在实际部署开始时收到“0 超出范围”错误。问题是我试图使用零长度副本并且我在特殊的 ARM functions/items 中引用 copyindex()referencedependsOn.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "RoleAssignments": {
            "type": "array"
        },
        "IdentityName": {
            "type": "string"
        },
        "StorageAccounts": {
            "type": "array"
        },
        "Enabled": {
            "type: "bool"
        }
    },
    "variables": {
        "IdentityResourceId": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('IdentityName'))]",
         "ComputedRoleAssignments": "[if(greater(length(parameters('RoleAssignments')), 0), 
                                              parameters('RoleAssignments'), 
                                              createArray(createObject('AccountName', 'dummy', 'Name', 'dummy', 'ResourceId', 'dummy', 'RoleDefinitionId', 'dummy', 'PrincipalType', 'dummy', 'Scope', 'dummy', 'PrincipalId', 'dummy')))]"

    },
    "resources": [
        { // Everything related to role assignments chokes completely, even if I unroll the loop
            "dependsOn": [
                "[variables('IdentityResourceId')]",
                "storageaccountcopy"
            ],
            "copy": {
                "name": "RoleAssignmentsCopy",
                "count": "[length(variables('ComputedRoleAssignments'))]"
            }, // Conditionally deploy based on parameters, but use the value from computed variables
            "condition": "[and(greater(length(parameters('RoleAssignments')), 0), parameters('Enabled'))]",
            "type": "Microsoft.Storage/storageAccounts/providers/roleAssignments",
            "apiVersion": "2020-04-01-preview",
            "name": "[concat(variables('ComputedRoleAssignments')[copyIndex()].AccountName, '/Microsoft.Authorization/', guid(variables('ComputedRoleAssignments')[copyIndex()].Name))]",
            "properties": {
                "roleDefinitionId": "[variables('ComputedRoleAssignments')[copyIndex()].RoleDefinitionId]",
                "principalId": "[if(and(empty(parameters('ComputedRoleAssignments')[copyIndex()].ResourceId), not(empty(parameters('ComputedRoleAssignments')[copyIndex()].PrincipalId))), 
                                variables('ComputedRoleAssignments')[copyIndex()].PrincipalId, 
                                reference(variables('ComputedRoleAssignments')[copyIndex()].ResourceId, '2018-11-30').PrincipalId)]",
                "scope": "[variables('ComputedRoleAssignments')[copyIndex()].Scope]",
                "principalType": "[variables('ComputedRoleAssignments')[copyIndex()].PrincipalType]"
            }
        },
        {   // Everthing related to storage account provisioning works fine, this was existing code
            "condition": "[and(greater(length(parameters('StorageAccounts')), 0), not(parameters('SkipStorageAccountProvisioning')), equals(parameters('StorageAccountProvisioningDefault'), 'true'))]",
            "copy": {
                "name": "storageaccountcopy",
                "count": "[length(parameters('StorageAccounts'))]"
            },
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2019-06-01",
            "name": "[parameters('StorageAccounts')[copyIndex()].AccountName]",
            "location": "[resourceGroup().location]",
            "sku": {
                "name": "[if(equals(parameters('StorageAccounts')[copyIndex()].AccountTypeOverride, parameters('StorageAccountTypeOverrideDefault')), parameters('StorageAccounts')[copyIndex()].AccountType, parameters('StorageAccounts')[copyIndex()].AccountTypeOverride)]",
                "tier": "Standard"
            },
            "kind": "[parameters('StorageAccounts')[copyIndex()].AccountKind]",
            "properties": {
                "networkAcls": {
                    "bypass": "AzureServices",
                    "virtualNetworkRules": [],
                    "ipRules": [],
                    "defaultAction": "Allow"
                },
                "supportsHttpsTrafficOnly": true,
                "encryption": {
                    "services": {
                        "file": {
                            "keyType": "Account",
                            "enabled": true
                        },
                        "blob": {
                            "keyType": "Account",
                            "enabled": true
                        }
                    },
                    "keySource": "Microsoft.Storage"
                }
            }
        }
    ]
}