Loops iterativos no Bicep

Este artigo mostra como usar a for sintaxe para iterar sobre itens em uma coleção. Esta funcionalidade é suportada a partir da v0.3.1. Você pode usar loops para definir várias cópias de um recurso, módulo, variável, propriedade ou saída. Use loops para evitar a repetição da sintaxe no arquivo Bicep e para definir dinamicamente o número de cópias a serem criadas durante a implantação. Para passar por um início rápido, consulte Guia de início rápido: criar várias instâncias.

Para usar loops para criar vários recursos ou módulos, cada instância deve ter um valor exclusivo para a propriedade name. Você pode usar o valor de índice ou valores exclusivos em matrizes ou coleções para criar os nomes.

Recursos de formação

Se preferir aprender sobre loops por meio de orientação passo a passo, consulte Criar modelos flexíveis do Bicep usando condições e loops.

Sintaxe de loop

Os loops podem ser declarados por:

  • Usando um índice inteiro. Essa opção funciona quando o cenário é: "Quero criar tantas instâncias". A função range cria uma matriz de inteiros que começa no índice inicial e contém o número de elementos especificados. Dentro do loop, você pode usar o índice inteiro para modificar valores. Para obter mais informações, consulte Índice inteiro.

    [for <index> in range(<startIndex>, <numberOfElements>): {
      ...
    }]
    
  • Usando itens em uma matriz. Essa opção funciona quando o cenário é: "Quero criar uma instância para cada elemento em uma matriz". Dentro do loop, você pode usar o valor do elemento de matriz atual para modificar valores. Para obter mais informações, consulte Elementos de matriz.

    [for <item> in <collection>: {
      ...
    }]
    
  • Usando itens em um objeto de dicionário. Essa opção funciona quando o cenário é: "Quero criar uma instância para cada item em um objeto". A função items converte o objeto em uma matriz. Dentro do loop, você pode usar propriedades do objeto para criar valores. Para obter mais informações, consulte Objeto de dicionário.

    [for <item> in items(<object>): {
      ...
    }]
    
  • Usando índice inteiro e itens em uma matriz. Essa opção funciona quando o cenário é: "Quero criar uma instância para cada elemento em uma matriz, mas também preciso do índice atual para criar outro valor." Para obter mais informações, consulte Matriz de loop e índice.

    [for (<item>, <index>) in <collection>: {
      ...
    }]
    
  • Adicionando uma implantação condicional. Essa opção funciona quando o cenário é: "Quero criar várias instâncias, mas para cada instância quero implantar somente quando uma condição for verdadeira." Para obter mais informações, consulte Loop with condition.

    [for <item> in <collection>: if(<condition>) {
      ...
    }]
    

Limites de loop

O uso de loops no Bicep tem estas limitações:

  • Os loops do bíceps só funcionam com valores que podem ser determinados no início da implantação.
  • As iterações de loop não podem ser um número negativo ou exceder 800 iterações.
  • Não é possível fazer loop de um recurso com recursos filho aninhados. Altere os recursos filho para recursos de nível superior. Consulte Iteração para um recurso filho.
  • Para fazer loop em vários níveis de propriedades, use a função de mapa lambda.

Índice inteiro

Para obter um exemplo simples de uso de um índice, crie uma variável que contenha uma matriz de cadeias de caracteres.

param itemCount int = 5

var stringArray = [for i in range(0, itemCount): 'item${(i + 1)}']

output arrayResult array = stringArray

A saída retorna uma matriz com os seguintes valores:

[
  "item1",
  "item2",
  "item3",
  "item4",
  "item5"
]

O próximo exemplo cria o número de contas de armazenamento especificadas no storageCount parâmetro. Ele retorna três propriedades para cada conta de armazenamento.

param location string = resourceGroup().location
param storageCount int = 2

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, storageCount): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

output storageInfo array = [for i in range(0, storageCount): {
  id: storageAcct[i].id
  blobEndpoint: storageAcct[i].properties.primaryEndpoints.blob
  status: storageAcct[i].properties.statusOfPrimary
}]

Observe que o índice i é usado na criação do nome do recurso da conta de armazenamento.

O próximo exemplo implanta um módulo várias vezes.

param location string = resourceGroup().location
param storageCount int = 2

var baseName = 'store${uniqueString(resourceGroup().id)}'

module stgModule './storageAccount.bicep' = [for i in range(0, storageCount): {
  name: '${i}deploy${baseName}'
  params: {
    storageName: '${i}${baseName}'
    location: location
  }
}]

output storageAccountEndpoints array = [for i in range(0, storageCount): {
  endpoint: stgModule[i].outputs.storageEndpoint
}]

Elementos de matriz

O exemplo a seguir cria uma conta de armazenamento para cada nome fornecido no storageNames parâmetro. Observe que a propriedade name para cada instância de recurso deve ser exclusiva.

param location string = resourceGroup().location
param storageNames array = [
  'contoso'
  'fabrikam'
  'coho'
]

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for name in storageNames: {
  name: '${name}${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

O próximo exemplo itera sobre uma matriz para definir uma propriedade. Ele cria duas sub-redes dentro de uma rede virtual. Observe que os nomes das sub-redes devem ser exclusivos.

param rgLocation string = resourceGroup().location

var subnets = [
  {
    name: 'api'
    subnetPrefix: '10.144.0.0/24'
  }
  {
    name: 'worker'
    subnetPrefix: '10.144.1.0/24'
  }
]

resource vnet 'Microsoft.Network/virtualNetworks@2020-07-01' = {
  name: 'vnet'
  location: rgLocation
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.144.0.0/20'
      ]
    }
    subnets: [for subnet in subnets: {
      name: subnet.name
      properties: {
        addressPrefix: subnet.subnetPrefix
      }
    }]
  }
}

Matriz e índice

O exemplo a seguir usa o elemento array e o valor de índice ao definir a conta de armazenamento.

param storageAccountNamePrefix string

var storageConfigurations = [
  {
    suffix: 'local'
    sku: 'Standard_LRS'
  }
  {
    suffix: 'geo'
    sku: 'Standard_GRS'
  }
]

resource storageAccountResources 'Microsoft.Storage/storageAccounts@2022-09-01' = [for (config, i) in storageConfigurations: {
  name: '${storageAccountNamePrefix}${config.suffix}${i}'
  location: resourceGroup().location
  sku: {
    name: config.sku
  }
  kind: 'StorageV2'
}]

O próximo exemplo usa os elementos de uma matriz e um índice para produzir informações sobre os novos recursos.

param location string = resourceGroup().location
param orgNames array = [
  'Contoso'
  'Fabrikam'
  'Coho'
]

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = [for name in orgNames: {
  name: 'nsg-${name}'
  location: location
}]

output deployedNSGs array = [for (name, i) in orgNames: {
  orgName: name
  nsgName: nsg[i].name
  resourceId: nsg[i].id
}]

Objeto de dicionário

Para iterar sobre elementos em um objeto de dicionário, use a função items, que converte o objeto em uma matriz. Use a value propriedade para obter propriedades nos objetos. Observe que os nomes de recursos nsg devem ser exclusivos.

param nsgValues object = {
  nsg1: {
    name: 'nsg-westus1'
    location: 'westus'
  }
  nsg2: {
    name: 'nsg-east1'
    location: 'eastus'
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = [for nsg in items(nsgValues): {
  name: nsg.value.name
  location: nsg.value.location
}]

Loop com condição

Para recursos e módulos, você pode adicionar uma if expressão com a sintaxe de loop para implantar condicionalmente a coleção.

O exemplo a seguir mostra um loop combinado com uma instrução condition. Neste exemplo, uma única condição é aplicada a todas as instâncias do módulo.

param location string = resourceGroup().location
param storageCount int = 2
param createNewStorage bool = true

var baseName = 'store${uniqueString(resourceGroup().id)}'

module stgModule './storageAccount.bicep' = [for i in range(0, storageCount): if(createNewStorage) {
  name: '${i}deploy${baseName}'
  params: {
    storageName: '${i}${baseName}'
    location: location
  }
}]

O próximo exemplo mostra como aplicar uma condição específica ao elemento atual na matriz.

resource parentResources 'Microsoft.Example/examples@2020-06-06' = [for parent in parents: if(parent.enabled) {
  name: parent.name
  properties: {
    children: [for child in parent.children: {
      name: child.name
      setting: child.settingValue
    }]
  }
}]

Implantar em lotes

Por padrão, os recursos do Azure são implantados em paralelo. Quando você usa um loop para criar várias instâncias de um tipo de recurso, essas instâncias são todas implantadas ao mesmo tempo. A ordem em que são criados não é garantida. Não há limite para o número de recursos implantados em paralelo, além do limite total de 800 recursos no arquivo Bicep.

Talvez você não queira atualizar todas as instâncias de um tipo de recurso ao mesmo tempo. Por exemplo, ao atualizar um ambiente de produção, convém escalonar as atualizações para que apenas um determinado número seja atualizado a qualquer momento. Você pode especificar que um subconjunto das instâncias seja agrupado em lote e implantado ao mesmo tempo. As outras instâncias aguardam a conclusão desse lote.

Para implantar instâncias em série de um recurso, adicione o decorador batchSize. Defina seu valor para o número de instâncias a serem implantadas simultaneamente. Uma dependência é criada em instâncias anteriores no loop, portanto, não inicia um lote até que o lote anterior seja concluído.

param location string = resourceGroup().location

@batchSize(2)
resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, 4): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

Para implantação sequencial, defina o tamanho do lote como 1.

O batchSize decorador está no namespace sys. Se você precisa diferenciar este decorador de outro item com o mesmo nome, prefacie o decorador com sys: @sys.batchSize(2)

Iteração para um recurso filho

Não é possível usar um loop para um recurso filho aninhado. Para criar mais de uma instância de um recurso filho, altere o recurso filho para um recurso de nível superior.

Por exemplo, suponha que você normalmente defina um serviço de arquivo e um compartilhamento de arquivos como recursos aninhados para uma conta de armazenamento.

resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'examplestorage'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  resource service 'fileServices' = {
    name: 'default'
    resource share 'shares' = {
      name: 'exampleshare'
    }
  }
}

Para criar mais de um compartilhamento de arquivos, mova-o para fora da conta de armazenamento. Você define o relacionamento com o recurso pai por meio da parent propriedade.

O exemplo a seguir mostra como criar uma conta de armazenamento, serviço de arquivo e mais de um compartilhamento de arquivos:

resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'examplestorage'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

resource service 'Microsoft.Storage/storageAccounts/fileServices@2021-06-01' = {
  name: 'default'
  parent: stg
}

resource share 'Microsoft.Storage/storageAccounts/fileServices/shares@2021-06-01' = [for i in range(0, 3): {
  name: 'exampleshare${i}'
  parent: service
}]

Coleções de recursos/módulos de referência

A função de modelo ARM retorna uma matriz de objetos que representam os estados de tempo de execução de references uma coleção de recursos. No Bicep, não há função de referências explícitas. Em vez disso, o uso da coleção simbólica é empregado diretamente e, durante a geração de código, o Bicep o traduz para um modelo ARM que utiliza a função de referências de modelo ARM. Para o recurso de tradução que transforma coleções simbólicas em modelos ARM usando a função de referências, é necessário ter a versão 0.20.X ou superior da CLI do Bicep. Além disso, no bicepconfig.json arquivo, a symbolicNameCodegen configuração deve ser apresentada e definida como true.

As saídas dos dois exemplos no índice inteiro podem ser escritas como:

param location string = resourceGroup().location
param storageCount int = 2

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, storageCount): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

output storageInfo array = map(storageAcct, store => {
  blobEndpoint: store.properties.primaryEndpoints
  status: store.properties.statusOfPrimary
})

output storageAccountEndpoints array = map(storageAcct, store => store.properties.primaryEndpoints)

Este arquivo Bicep é transempilhado no seguinte modelo JSON ARM que utiliza a references função:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "languageVersion": "1.10-experimental",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "storageCount": {
      "type": "int",
      "defaultValue": 2
    }
  },
  "resources": {
    "storageAcct": {
      "copy": {
        "name": "storageAcct",
        "count": "[length(range(0, parameters('storageCount')))]"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}storage{1}', range(0, parameters('storageCount'))[copyIndex()], uniqueString(resourceGroup().id))]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage"
    }
  },
  "outputs": {
    "storageInfo": {
      "type": "array",
      "value": "[map(references('storageAcct', 'full'), lambda('store', createObject('blobEndpoint', lambdaVariables('store').properties.primaryEndpoints, 'status', lambdaVariables('store').properties.statusOfPrimary)))]"
    },
    "storageAccountEndpoints": {
      "type": "array",
      "value": "[map(references('storageAcct', 'full'), lambda('store', lambdaVariables('store').properties.primaryEndpoints))]"
    }
  }
}

Observação no modelo JSON ARM anterior, deve ser definido como 1.10-experimental, languageVersion e o elemento resource é um objeto em vez de uma matriz.

Próximos passos

  • Para saber mais sobre como criar arquivos Bicep, consulte arquivo.