Loops iterativos no Bicep

Este artigo mostra como usar a sintaxe for para iterar sobre itens em uma coleção. Há suporte para essa funcionalidade da v0.3.1 em diante. É possível usar loops para definir várias cópias de um recurso, módulo, variável, propriedade ou saída. Use loops para evitar a sintaxe repetida 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, confira 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 treinamento

Se você preferir aprender sobre loops por meio de diretrizes passo a passo, confira Criar modelos Bicep flexíveis usando condições e loops.

Sintaxe de loop

Os loops podem ser declarados por:

  • Usando um índice de número inteiro. Essa opção funciona quando seu cenário é: "Quero criar este número de 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, é possível usar o índice de números inteiros para modificar valores. Para saber mais, consulte Índice de números inteiros.

    [for <index> in range(<startIndex>, <numberOfElements>): {
      ...
    }]
    
  • Usando itens em uma matriz. Essa opção funciona quando seu 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 os valores. Para obter mais informações, confira 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, é possível usar propriedades do objeto para criar valores. Para saber mais, confira Objeto de dicionário.

    [for <item> in items(<object>): {
      ...
    }]
    
  • Usando o índice de números inteiros 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, confira Índice e matriz de loop.

    [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 desejo implantar cada instância somente quando uma condição for verdadeira". Para obter mais informações, confira Loop com condição.

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

Limites de loop

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

  • Os loops do Bicep 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. Alterar os recursos filho para recursos de nível superior. Confira Iteração para um recurso filho.
  • Para executar um loop em vários níveis de propriedades, use a função de mapa lambda.

Índice de número inteiro

Para obter um exemplo simples de como usar 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 elementos:

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

O exemplo a seguir cria o número de contas de armazenamento especificadas no parâmetro storageCount. 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 exemplo a seguir 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 da matriz

O exemplo a seguir cria uma conta de armazenamento para cada nome fornecido no parâmetro storageNames. 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 exemplo a seguir itera em uma matriz para definir uma propriedade. Ele cria duas sub-redes em uma rede virtual. Observe que os nomes de sub-rede 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 de matriz 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 exemplo a seguir usa os elementos de uma matriz e um índice para gerar 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 Itens, que converte o objeto em uma matriz. Use a propriedade value para obter propriedades sobre os 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

Em recursos e módulos, é possível adicionar uma expressão if com a sintaxe de loop para implantar condicionalmente a coleção.

O exemplo a seguir mostra um loop combinado com uma instrução de condição. 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 exemplo a seguir 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 eles são criados não é garantida. Não há limites para o número de recursos implantados em paralelo, com exceção 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 balancear as atualizações para que apenas um determinado número seja atualizado por vez. Você pode especificar que um subconjunto das instâncias seja agrupado em lote e implantado ao mesmo tempo. As outras instâncias esperam esse lote ser concluído.

Para implantar em série as instâncias de um recurso, adicione o decorador batchSize. Defina seu valor como o número de instâncias a serem implantadas simultaneamente. Uma dependência é criada em instâncias anteriores no loop, de modo que ela não inicie um lote até que o lote anterior esteja 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'
}]

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

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

Iteração para um recurso filho

Você não pode 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 compartilhamento de arquivo 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 arquivo, mova-o para fora da conta de armazenamento. Você define a relação com o recurso pai por meio da propriedade parent.

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

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 recurso/módulo de referência

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

As saídas dos dois exemplos no Índice inteiro podem ser gravadas 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)

Esse arquivo Bicep é transpilado para o seguinte modelo JSON do ARM que utiliza a função references:

{
  "$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))]"
    }
  }
}

Observe que, no modelo JSON do ARM anterior, languageVersion deve ser definido como 1.10-experimentale o elemento do recurso é um objeto em vez de uma matriz.

Próximas etapas

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