Cicli iterativi in Bicep

Questo articolo illustra come usare la sintassi for per scorrere gli elementi in una raccolta. Questa funzionalità è supportata a partire dalla versione 0.3.1 successiva. È possibile usare cicli per definire più copie di una risorsa, un modulo, una variabile, una proprietà o un output. Usare i cicli per evitare la sintassi ripetuta nel file Bicep e impostare dinamicamente il numero di copie da creare durante la distribuzione. Per un avvio rapido, vedere Avvio rapido: creare istanze multiple.

Per usare i cicli per creare più risorse o moduli, ogni istanza deve avere un valore univoco per la proprietà name. È possibile usare il valore di indice o i valori univoci in matrici o raccolte per creare i nomi.

Risorse di formazione

Se si preferisce ottenere informazioni sui cicli per mezzo di materiale sussidiario passo-passo, vedere Creare modelli Bicep flessibili usando condizioni e cicli.

Sintassi del ciclo

I cicli possono essere dichiarati da:

  • Utilizzo di un indice di numeri interi. Questa opzione funziona quando lo scenario è: "Voglio creare queste numerose istanze". La funzione intervallo crea un array di numeri interi che inizia in corrispondenza dell'indice iniziale e contiene il numero di elementi specificati. All'interno del ciclo è possibile usare l'indice di numeri interi per modificare i valori. Per altre informazioni, vedere Indice numeri interi.

    [for <index> in range(<startIndex>, <numberOfElements>): {
      ...
    }]
    
  • Uso di elementi in un array. Questa opzione funziona quando lo scenario è: "Si desidera creare un'istanza per ogni elemento in un array". All'interno del ciclo è possibile usare il valore dell'elemento dell'array corrente per modificare i valori. Per altre informazioni, vedere Elementi dell'array.

    [for <item> in <collection>: {
      ...
    }]
    
  • Utilizzo di elementi in un oggetto dizionario. Questa opzione funziona quando lo scenario è: "Si desidera creare un'istanza per ogni elemento in un oggetto". La funzione elementi converte l'oggetto in un array. All'interno del ciclo è possibile usare le proprietà dell'oggetto per creare valori. Per altre informazioni, vedere Oggetto dizionario.

    [for <item> in items(<object>): {
      ...
    }]
    
  • Uso di un indice di numeri interi e di elementi in un array. Questa opzione funziona quando lo scenario è: "Si desidera creare un'istanza per ogni elemento in un array, ma è necessario anche l'indice corrente per creare un altro valore". Per altre informazioni, vedere Array di cicli e indice.

    [for (<item>, <index>) in <collection>: {
      ...
    }]
    
  • Aggiunta di una distribuzione condizionale. Questa opzione funziona quando lo scenario è: "Si vogliono creare istanze multiple, ma per ogni istanza si desidera distribuire solo quando una condizione è true". Per altre informazioni, vedere Ciclo con condizione.

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

Limiti del ciclo

L'uso di cicli in Bicep presenta queste limitazioni:

  • I cicli Bicep funzionano solo con valori che possono essere determinati all'inizio della distribuzione.
  • Le iterazioni del ciclo non possono essere un numero negativo o superare 800 iterazioni.
  • Non è possibile eseguire il ciclo di una risorsa con risorse figlio annidate. Modificare le risorse figlio in risorse di primo livello. Vedere Iterazione di una risorsa figlio.
  • Per eseguire un ciclo su più livelli di proprietà, usare la funzione mappa lambda.

Indice intero

Per un semplice esempio di utilizzo di un indice, creare una variabile contenente un array di stringhe.

param itemCount int = 5

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

output arrayResult array = stringArray

L'output restituisce un array con i valori seguenti:

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

Nell'esempio seguente viene creato il numero di account di archiviazione specificati parametro storageCount. Restituisce tre proprietà per ogni account di archiviazione.

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

resource storageAcct 'Microsoft.Storage/storageAccounts@2023-05-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
}]

Si noti che l'indice i viene usato per creare il nome della risorsa dell'account di archiviazione.

Nell'esempio seguente viene distribuito più volte un modulo.

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
}]

Elementi array

Nell'esempio seguente viene creato un account di archiviazione per ogni nome specificato nel parametro storageNames. Si noti che la proprietà nome per ogni istanza di risorsa deve essere univoca.

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

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

L'esempio successivo scorre un array per definire una proprietà. Crea due subnet all'interno di una rete virtuale. Si noti che i nomi delle subnet devono essere univoci.

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@2023-11-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
      }
    }]
  }
}

Array e indice

Nell'esempio seguente vengono usati sia l'elemento dell'array che il valore di indice durante la definizione dell'account di archiviazione.

param storageAccountNamePrefix string

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

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

Nell'esempio seguente vengono usati sia gli elementi di un array che un indice per restituire informazioni sulle nuove risorse.

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

resource nsg 'Microsoft.Network/networkSecurityGroups@2023-11-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
}]

Oggetto Dizionario

Per scorrere gli elementi in un oggetto dizionario, usare la funzione elementi, che converte l'oggetto in un array. Utilizzare la proprietà value per ottenere proprietà sugli oggetti. Si noti che i nomi delle risorse nsg devono essere univoci.

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

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

Ciclo con condizione

Per risorse e moduli, è possibile aggiungere un'espressione if con la sintassi del ciclo per distribuire in modo condizionale la raccolta.

Nell'esempio seguente viene illustrato un ciclo combinato con un'istruzione condizione. In questo esempio viene applicata una singola condizione a tutte le istanze del modulo.

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
  }
}]

Nell'esempio seguente viene illustrato come applicare una condizione specifica per l'elemento corrente nell'array.

resource parentResources 'Microsoft.Example/examples@2024-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
    }]
  }
}]

Distribuire in batch

Per impostazione predefinita, le risorse Azure vengono distribuite in parallelo. Quando si usa un ciclo per creare istanze multiple di un tipo di risorsa, tali istanze vengono distribuite contemporaneamente. L'ordine di creazione non è garantito. Non si applica alcun limite al numero di risorse distribuite in parallelo, oltre al limite totale di 800 risorse nel file Bicep.

Potrebbe non essere necessario aggiornare tutte le istanze di un tipo di risorsa contemporaneamente. Ad esempio, quando si aggiorna un ambiente di produzione, è consigliabile sfalsare gli aggiornamenti per aggiornarne solo un determinato numero in un dato momento. È possibile specificare che un subset delle istanze deve essere raggruppato e distribuito contemporaneamente. Le altre istanze attendono il completamento del batch.

Per distribuire in modo seriale le istanze di una risorsa, aggiungere l'elemento decorator batchSize. Impostarne il valore sul numero di istanze da distribuire contemporaneamente. Una dipendenza viene creata nelle istanze precedenti del ciclo, quindi non avvia un batch fino al completamento del batch precedente.

param location string = resourceGroup().location

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

Per la distribuzione sequenziale, impostare le dimensioni del batch su 1.

L'elemento decorator batchSize si trova nello spazio dei nomi sys. Se è necessario distinguere questo elemento decorator da un altro elemento con lo stesso nome, anteporre al decorator con sys: @sys.batchSize(2)

Iterazione di una risorsa figlio

Per creare più di un'istanza di una risorsa figlio, entrambi i file Bicep seguenti funzioneranno.

Risorse figlio annidate

param location string = resourceGroup().location

resource stg 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: 'examplestorage'
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  resource service 'fileServices' = {
    name: 'default'
    resource share 'shares' = [for i in range(0, 3): {
      name: 'exampleshare${i}'
    }]
  }
}

Risorse figlio di primo livello

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

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

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

Raccolte di risorse/moduli di riferimento

La funzione references del modello di ARM restituisce un array di oggetti che rappresentano gli stati di runtime di una raccolta di risorse. In Bicep non esiste alcuna funzione di riferimenti espliciti. L'utilizzo della raccolta simbolica viene invece usato direttamente e, durante la generazione del codice, Bicep lo converte in un modello di Resource Manager che usa la funzione dei riferimenti al modello di Resource Manager. Per la funzionalità di conversione che trasforma le raccolte simboliche in modelli di ARM usando la funzione riferimenti, è necessario avere l'interfaccia della riga di comando di Bicep versione 0.20.X o successiva. Inoltre, nel file bicepconfig.json, l'impostazione symbolicNameCodegen deve essere presentata e impostata su true.

Gli output dei due esempi nell'indice di numeri interi possono essere scritti come segue:

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

resource storageAcct 'Microsoft.Storage/storageAccounts@2023-05-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)

Questo file Bicep viene sottoposto a transpile nel modello ARM JSON seguente che usa la funzione 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": "2023-04-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))]"
    }
  }
}

Nota nel modello ARM JSON precedente, languageVersion deve essere impostato su 1.10-experimental e l'elemento della risorsa è un oggetto anziché un array.

Passaggi successivi

  • Per informazioni sulla creazione di file Bicep, vedere file.