Problem


Bicep's syntax is already pretty clean, but sometimes you run into situations like needing to concatenate strings or format names repeatedly, doing small calculations based on input parameters, or having long, messy expressions inside resource definitions. Let’s say you want to automatically generate a series of VM names like web-01, web-02, and so on. Without functions, you’d have to write 'web-${padLeft(string(i+1), 2, '0')}' every time, makes you work 996. Let’s see how to make this easier.

Solution


User-Defined Function

A Bicep function typically looks like this:

@<decorator>(<argument>)
func <user-defined-function-name> (<argument-name> <data-type>, <argument-name> <data-type>, ...) <function-data-type> => <expression>

Functions can take parameters and specify a return type.

In this case, just create a function like below:

func generateVmName(prefix string, index int) string => '${prefix}-${padLeft(string(index + 1), 2, '0')}'

// Usage
resource vms 'Microsoft.Compute/virtualMachines@2022-08-01' = [for i in range(0, vmCount): {
  name: generateVmName('web', i)
  ...
}]

Full Bicep File

This example will deploy a set of Azure Virtual Machines with automatically generated names like web-01, web-02, etc.

func generateVmName(prefix string, index int) string => '${prefix}-${padLeft(string(index + 1), 2, '0')}'

@minLength(1)
param vmPrefix string = 'web'

@minValue(1)
@maxValue(10)
param vmCount int = 2

param location string = resourceGroup().location

@description('The subnet resource ID where all VMs will be deployed')
param subnetId string

@secure()
param adminPassword string

param adminUsername string = 'azureuser'

var vmSize = 'Standard_B1s'
var vmImage = {
  publisher: 'Canonical'
  offer: '0001-com-ubuntu-server-jammy'
  sku: '22_04-lts-gen2'
  version: 'latest'
}

resource vms 'Microsoft.Compute/virtualMachines@2022-08-01' = [
  for i in range(0, vmCount): {
    name: generateVmName(vmPrefix, i)
    location: location
    properties: {
      hardwareProfile: {
        vmSize: vmSize
      }
      storageProfile: {
        imageReference: vmImage
        osDisk: {
          createOption: 'FromImage'
          managedDisk: {
            storageAccountType: 'Standard_LRS'
          }
        }
      }
      osProfile: {
        computerName: generateVmName(vmPrefix, i)
        adminUsername: adminUsername
        adminPassword: adminPassword
        linuxConfiguration: {
          disablePasswordAuthentication: false
        }
      }
      networkProfile: {
        networkInterfaces: [
          {
            id: nicResources[i].id
          }
        ]
      }
    }
  }
]

resource nicResources 'Microsoft.Network/networkInterfaces@2022-09-01' = [
  for i in range(0, vmCount): {
    name: '${generateVmName(vmPrefix, i)}-nic'
    location: location
    properties: {
      ipConfigurations: [
        {
          name: 'ipconfig1'
          properties: {
            subnet: {
              id: subnetId
            }
            privateIPAllocationMethod: 'Dynamic'
          }
        }
      ]
    }
  }
]

output vmNames array = [for i in range(0, vmCount): generateVmName(vmPrefix, i)]
output nicNames array = [for i in range(0, vmCount): '${generateVmName(vmPrefix, i)}-nic']

Run this script:

az deployment group create --resource-group bicep-demo-group --template-file main.bicep --parameters adminPassword=W0rk996S!ickICU

This script will create the network, subnet, NICs, and VMs automatically.

You will get: