Boucles itératives dans Bicep

Cet article explique comment utiliser la syntaxe for pour effectuer une itération sur des éléments d’une collection. Cette fonctionnalité est prise en charge à partir de v0.3.1. Vous pouvez utiliser des boucles pour définir plusieurs copies d’une ressource, d’un module, d’une variable, d’une propriété ou d’une sortie. Utilisez des boucles pour éviter de répéter une syntaxe dans votre fichier Bicep, ainsi que pour définir de façon dynamique le nombre de copies à créer au cours du déploiement. Pour effectuer un démarrage rapide, consultez Démarrage rapide : créer plusieurs instances.

Pour utiliser des boucles pour créer plusieurs ressources ou modules, chaque instance doit avoir une valeur unique pour la propriété de nom. Vous pouvez utiliser la valeur d’index ou des valeurs uniques dans des tableaux ou des collections pour créer les noms.

Ressources de formation

Si vous préférez découvrir les boucles via des instructions d’aide pas à pas, consultez Créer des modèles Bicep flexibles en utilisant des conditions et des boucles.

Syntaxe de boucle

Des boucles peuvent être déclarées comme suit :

  • En utilisant un index d’entiers. Cette option fonctionne lorsque votre scénario est : « Je souhaite créer ce nombre d’instances ». La fonction de plage de données crée un tableau d’entiers qui commence à l’index de début et contient le nombre d’éléments spécifiés. Dans la boucle, vous pouvez utiliser l’index d’entiers pour modifier des valeurs. Pour plus d’informations, consultez Index d’entiers.

    [for <index> in range(<startIndex>, <numberOfElements>): {
      ...
    }]
    
  • En utilisant les éléments d’un tableau. Cette option fonctionne lorsque le scénario est le suivant : « Je souhaite créer une instance pour chaque élément dans un tableau ». Dans la boucle, vous pouvez utiliser la valeur de l’élément de matrice actuel pour modifier les valeurs. Pour plus d’informations, consultez Éléments de tableau.

    [for <item> in <collection>: {
      ...
    }]
    
  • En utilisant les éléments d’un objet dictionnaire. Cette option fonctionne quand votre scénario est : « Je souhaite créer une instance pour chaque élément d’un objet ». La fonction des éléments convertit l’objet en tableau. Au sein de la boucle, vous pouvez utiliser des propriétés de l’objet pour créer des valeurs. Pour plus d’informations, consultez Objet dictionnaire.

    [for <item> in items(<object>): {
      ...
    }]
    
  • En utilisant un index d’entiers et les éléments d’un tableau. Cette option fonctionne quand votre scénario est : « Je souhaite créer une instance pour chaque élément d’un tableau, mais j’ai également besoin de l’index actuel pour créer une autre valeur ». Pour plus d’informations, consultez Tableau de boucles et index.

    [for (<item>, <index>) in <collection>: {
      ...
    }]
    
  • En ajoutant un déploiement conditionnel. Cette option fonctionne quand votre scénario est : « Je souhaite créer plusieurs instances mais, pour chaque instance, je ne souhaite déployer que quand une condition est vraie ». Pour plus d’informations, consultez Boucle avec condition.

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

Limites des boucles

L’utilisation de boucles dans Bicep est sujette aux limitations suivantes :

  • Les boucles Bicep fonctionnent uniquement avec des valeurs qui peuvent être déterminées au début du déploiement.
  • Le nombre d’itérations de boucle ne peut être ni négatif, ni supérieur à 800.
  • Impossible de faire une boucle sur une ressource avec des ressources enfants imbriquées. Remplacez les ressources enfants par les ressources de niveau supérieur. Voir Itération d’une ressource enfant.
  • Pour effectuer une boucle sur plusieurs niveaux de propriétés, utilisez la fonction map lambda.

Index d’entiers

Pour obtenir un exemple simple d’utilisation d’un index, créez une variable contenant un tableau de chaînes.

param itemCount int = 5

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

output arrayResult array = stringArray

La sortie retourne un tableau avec les valeurs suivantes :

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

L’exemple suivant crée le nombre de comptes de stockage spécifié dans le paramètre storageCount. Il retourne trois propriétés pour chaque compte de stockage.

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

Notez que l’index i est utilisé pour créer le nom de ressource du compte de stockage.

L’exemple suivant déploie un module plusieurs fois.

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

Éléments de tableau

L’exemple suivant crée un compte de stockage par nom fourni dans le paramètre storageNames. Notez que la propriété de nom pour chaque instance de ressource doit être unique.

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

L’exemple suivant effectue une itération sur un tableau pour définir une propriété. Il crée deux sous-réseaux au sein d’un réseau virtuel. Notez que les noms de sous-réseau doivent être uniques.

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

Tableau et index

L’exemple suivant utilise à la fois l’élément de tableau et la valeur d’index lors de la définition du compte de stockage.

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

L’exemple suivant utilise à la fois les éléments d’un tableau et un index pour produire des informations sur les nouvelles ressources.

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

Objet dictionnaire

Pour effectuer une itération sur des éléments d’un objet dictionnaire, utilisez la fonction items qui convertit l’objet en tableau. Utilisez la propriété value pour obtenir les propriétés sur les objets. Notez que les noms de ressources nsg doivent être uniques.

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

Boucle avec condition

Pour des ressources et modules, vous pouvez ajouter une expression if avec la syntaxe de boucle pour déployer la collection de manière conditionnelle.

L’exemple suivant montre une boucle associée à une instruction de condition. Dans cet exemple, une condition unique est appliquée à toutes les instances du module.

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

L’exemple suivant montre comment appliquer une condition spécifique de l’élément actuel dans le tableau.

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

Déployer par lots

Par défaut, les ressources Azure sont déployées en parallèle. Lorsque vous utilisez une boucle pour créer plusieurs instances d’un type de ressource, ces instances sont toutes déployées en même temps. L’ordre de création n’est pas garanti. Il n’existe aucune limite au nombre de ressources déployées en parallèle, à l’exception de la limite totale de 800 ressources dans le fichier Bicep.

Il se peut que vous ne souhaitiez pas mettre à jour toutes les instances d’un type de ressource en même temps. Par exemple, lors de la mise à jour d’un environnement de production, vous souhaiterez échelonner les mises à jour afin que seulement un certain nombre soient mises à jour à un moment donné. Vous pouvez spécifier qu’un sous-ensemble des instances soit traité par lots ensemble et déployé en même temps. Les autres instances attendent que ce lot soit finalisé.

Pour déployer en série des instances d’une ressource, ajoutez l’élément décoratif BatchSize. Définissez sa valeur comme le nombre d’instances à déployer simultanément. Une dépendance est créée sur les instances précédentes de la boucle, afin de ne pas démarrer un lot tant que le précédent n’est pas terminé.

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

Pour un déploiement séquentiel, définissez la taille du lot sur 1.

L’élément décoratif batchSize se trouve dans l’espace de noms sys. Si vous devez différencier cet élément décoratif d'un autre élément portant le même nom, faites précéder l’élément décoratif de sys: @sys.batchSize(2)

Itération d’une ressource enfant

Vous ne pouvez pas utiliser une boucle pour une ressource enfant imbriquée. Pour créer plusieurs instances d’une ressource enfant, remplacez la ressource enfant par une ressource de niveau supérieur.

Supposons, par exemple, que vous définissiez habituellement un service de fichiers et un partage de fichiers en tant que ressources imbriquées pour un compte de stockage.

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

Pour créer plusieurs partages de fichiers, déplacez-le en dehors du compte de stockage. Vous définissez la relation avec la ressource parente par le biais de la propriété parent.

L’exemple suivant montre comment créer un compte de stockage, un service de fichiers et plusieurs partages de fichiers :

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

Collections de ressources/modules de référence

La fonction references de modèle ARM retourne un tableau d’objets représentant les états d’exécution d’une collection de ressources. Dans Bicep, il n’existe aucune fonction de références explicite. Au lieu de cela, l’utilisation de la collection symbolique est employée directement et, lors de la génération de code, Bicep la traduit en modèle ARM qui utilise la fonction de références de modèle ARM. Pour la fonctionnalité de traduction qui transforme des collections symboliques en modèles ARM en utilisant la fonction de références, il est nécessaire d’avoir Bicep CLI version 0.20.X ou version supérieure. En outre, dans le fichier bicepconfig.json, le paramètre symbolicNameCodegen doit être présenté et défini sur true.

Les sorties des deux exemples dans l’Index d’entiers peuvent être écrites comme suit :

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)

Ce fichier Bicep est transpilé dans le modèle ARM JSON suivant qui utilise la fonction 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))]"
    }
  }
}

Notez que dans le modèle ARM JSON précédent, languageVersion doit être défini sur 1.10-experimental, et que l’élément de ressource est un objet au lieu d’un tableau.

Étapes suivantes

  • Pour savoir comment créer des fichiers Bicep, consultez fichier.