First look at Project Bicep, an Azure Resource Manager templates DSL
Tags: azure, devops, biceplang, iac
Published: 2020-09-18 16:38:06

There are many reasons to use infrastructure as code to provision your cloud infrastructure.  Among them, there is the opportunity to document your infrastructure with text files applied to deploy it, and the potentiality to update or modify part of the infrastructure without the need to rebuild everything.
I use ARM templates for a long time, and when I build a complex set of resources, I often have a dilemma between, readability and clearness and reusability. I can have a simple template, easy to read but it will not be easy to generalize it for several deployments across environments. 
For example, naming convention; if you want to enforce the naming convention in your template you may end up by creating complex user functions and variables that will make templates hard to read and hard to modify.  
The JSON syntax used by ARM can be complex. Learning it takes time and effort. It isn't flexible, you need to have you the five sections (parameters, variables, functions, resources, and output). Often, I see teams starting a project with ARM template and ending it with PowerShell, Azure CLI, or the portal. Sometimes, there are only one or two people with enough experiments in ARM and only them work on Infra As Code, make them a constraint in the deployment process.

But recently Microsoft started a new project to help you to speed up Infra As Code in Azure; project Bicep. Bicep is a Domain Specific Language for ARM Templates easier to learn and manipulate.  It compiles .bicep files into ARM JSON to deploy resources in Azure, making the JSON syntax an intermediate language between you and Azure API. 
It’s still experimental, it’s not recommended to use it in production yet. 

Let’s deep dive into the language 

First, you need to install the Bicep binary. Check the instruction on the [Project Github Page]( https://github.com/Azure/bicep/blob/master/docs/installing.md)

Then you can create your first Bicep file. Create an empty Bicep file in a directory called test.bicep. Then type:

```powershell
Bicep build test.bicep
``` 

Let’s try to build a more practical example by building a VM using the Bicep language. We need a storage account, a Vnet, a subnet, and a VM with a data disk and a NIC. 

As for a JSON project you need to start with inputs. In ARM template inputs are in two sections, parameters for user's inputs and variables for dynamic and static values.

In Bicep grammar, a parameter can be declared by using the form

```powershell 
param name type
``` 

As your Bicep file will be built into a JSON file you should follow the camel case (myResource) naming convention if possible. But nothing will happen if you forget the rule

There are five possible values, string, int, array, object, and bool. If you think something is missing, securestring and secureobject. This is not the case. They are implemented as a modifier for string and object type.

```powershell
param localAdminPassword string {
    secure: true
}
``` 

You can see the {} to introduce one or more modifiers to your param. Modifiers are similar to those you can see in JSON ARM Template. You can implement Allowed value, Default value, Limit for string and arrays, minimum and maximum value for an integer. You can also add a description. 

```powershell
param vmPrefix string {
    minLength: 1
    maxLength: 9
}
``` 

Variables can be used as constants, values that never change, and calculated value.

To declare a variable:

```powershell 
var defaultLocation = 'francecentral'
``` 

you can use some calculations. You can for example build the server name by concatenating two parameters.

```powershell
param environmentName string {
  allowed: [
    'prod'
    'dev'
  ]
}

param vmPrefix string {
    minLength: 1
    maxLength: 9
}

var defaultVmName = '${vmPrefix}-${environmentName}'
```

You can also use the Concat function, just like in ARM template 

```powershell
var VmName = concat('${vmPrefix}','-','${environmentName}')
``` 

The difference between the two, the first one will use the Format ARM function and the second the Concat ARM function.
You can also use expressions in Bicep. You can use comparison operators, math expression, and common ARM template expression such as resourceGroup()

```powershell
var diskSku = environmentName == 'prod' ? 'Premium_LRS' : 'Standard_LRS'
``` 

Resource definition is a simplification of the ARM Templates resource declaration. A resource in Bicep starts with the resource keyword and an identifier. The identifier is not the resource name, the value is used to reference a resource in another resource in your Bicep file. Following the identifier, the resource type with its API version in the format xxx/xxx@20XX-XX-XX and the resource body.

```powershell
  resource vmDataDisk 'Microsoft.Compute/disks@2019-07-01' = {
    name: '${defaultVmName}-vhd'
    location: defaultLocation
    sku: {
        name: 'Premium_LRS'
    }
    properties: {
        diskSizeGB: 32
        creationData: {
            createOption: 'empty'
        }
    }
``` 

As you can see the resource body is a simplified version of the JSON version. What’s it’s impressive (for me at least) is that you do not have to manage dependency. Bicep handles it automatically.
In the previous example, we created a data disk resource with vmDataDisk as identifier. It can be used in the VM to get the ID needed to attach the disk.

```powershell
        dataDisks: [
          {
            name: '${defaultVmName}-vhd'
            createOption: 'attach'
            caching: 'ReadOnly'
            lun: 0
            managedDisk: {
              id: vmDataDisk.id
            }
          }
        ]
``` 

To attach a disk you don’t have to use resourceID() as in JSON, you just need to use the identifier. Bicep will translate into resourceId() for you in the generated JSON file.  

```json
 "managedDisk": {
                "id": "[resourceId('Microsoft.Compute/disks', format('{0}-vhd', variables('defaultVmName')))]"
``` 

And more, Bicep creates also the dependsOn clause needed for deployment orders.

```json
"dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
        "[resourceId('Microsoft.Compute/disks', format('{0}-vhd', variables('defaultVmName')))]",
        "[resourceId('Microsoft.Network/networkInterfaces', variables('defaultVmNicName'))]"
      ]
```

Take a look at the entire solution, A Vnet with one subnet, a storage account for vm diagnostic, a data disk, a nic and a VM.

```powershell
// Global Data
param environmentName string {
  allowed: [
    'prod'
    'dev'
  ]
}
var defaultLocation = resourceGroup().location

// Storage Account data
param storageAccountName string {
    minLength: 3
    maxLength: 24

}
var sku = environmentName == 'prod' ? 'Standard_GRS' : 'Standard_LRS'

resource diagsAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {
    name: storageAccountName
    location: defaultLocation
    sku: {
      name: sku
    }
    kind: 'Storage'
  }


// Vm Related Data
param numberOfVM int {
    minValue: 1
    maxValue: 3
}
var diskSku = environmentName == 'prod' ? 'Premium_LRS' : 'Standard_LRS'
param vmSku string {
    allowed: [
        'Standard_F2s'
        'Standard_B2ms'
      ] 
}
param vmOS string {
    default: '2019-Datacenter'
    allowed: [
        '2016-Datacenter'
        '2016-Datacenter-Server-Core'
        '2016-Datacenter-Server-Core-smalldisk'
        '2019-Datacenter'
        '2019-Datacenter-Server-Core'
        '2019-Datacenter-Server-Core-smalldisk'
      ] 
}
param localAdminPassword string {
    secure: true
    metadata: {
        description: 'password for the windows VM'
    }
}
param vmPrefix string {
    minLength: 1
    maxLength: 9
}
var defaultVmName = '${vmPrefix}-${environmentName}'
var defaultVmNicName = '${defaultVmName}-nic'

resource vmNic 'Microsoft.Network/networkInterfaces@2017-06-01' = {
    name: defaultVmNicName
    location: defaultLocation
    properties: {
      ipConfigurations: [
        {
          name: 'ipconfig1'
          properties: {
            subnet: {
              id: '${vnet.id}/subnets/front'
            }
            privateIPAllocationMethod: 'Dynamic'
          }
        }
      ]
    }
  }

  resource vmDataDisk 'Microsoft.Compute/disks@2019-07-01' = {
    name: '${defaultVmName}-vhd'
    location: defaultLocation
    sku: {
        name: 'Premium_LRS'
    }
    properties: {
        diskSizeGB: 32
        creationData: {
            createOption: 'empty'
        }
    }

  }

  resource vm 'Microsoft.Compute/virtualMachines@2019-07-01' = {
    name: defaultVmName
    location: defaultLocation
    properties: {
      osProfile: {
        computerName: defaultVmName
        adminUsername: 'localadm'
        adminPassword: localAdminPassword
        windowsConfiguration: {
          provisionVmAgent: true
        }
      }
      hardwareProfile: {
        vmSize: ' Standard_F2s'
      }
      storageProfile: {
        imageReference: {
          publisher: 'MicrosoftWindowsServer'
          offer: 'WindowsServer'
          sku: vmOS
          version: 'latest'
        }
        osDisk: {
          createOption: 'FromImage'
        }
        dataDisks: [
          {
            name: '${defaultVmName}-vhd'
            createOption: 'attach'
            caching: 'ReadOnly'
            lun: 0
            managedDisk: {
              id: vmDataDisk.id
            }
          }
        ]
      }
      networkProfile: {
        networkInterfaces: [
          {
            properties: {
              primary: true
            }
            id: vmNic.id
          }
        ]
      }
      diagnosticsProfile: {
        bootDiagnostics: {
          enabled: true
          storageUri: diagsAccount.properties.primaryEndpoints.blob
        }
      }
    }
  }

//Network related data
param vnetName string {
    metadata: {
        description: 'name of the Virtual network'
    }
}
var vnetConfig = {
    vnetprefix: '10.0.0.0/21'
    subnet: {
            name: 'front'
            addressPrefix: '10.0.0.0/24'
        }
} 


  resource vnet 'Microsoft.Network/virtualNetworks@2020-05-01' = {
    name: vnetName
    location: defaultLocation
    properties: {
      addressSpace: {
        addressPrefixes: [
          vnetConfig.vnetprefix
        ]
      }
      subnets: [ 
        {
          name: vnetConfig.subnet.name
          properties: {
            addressPrefix: vnetConfig.subnet.addressPrefix
          }
        }

      ]
    }
  }
```

First, you can see variables, parameters and resources are totally mixed. I like to manage resources this way, having variables and parameters related to one resource is simpler to use. Bicep allows this behavior.  
You can also notice comments in the document, Bicep allow single line comments (//) and multi-lines comments (/* */

Now you can build the Bicep file.

```powershell
Bicep build vm.bicep
``` 

It will create a vm.json file in the same directory. You can now use the JSON file with new-azResourceGroupDeployment to create your resources in Azure. 

Bicep project is still in the alpha phase. You may find some bugs (in this case you can create an [issue on Github](https://github.com/Azure/bicep/issues)) and there [some now limitations](https://github.com/Azure/bicep#known-limitations). For the moment you can only create basic Bicep files. The project is active and new features will enhance the tool. Bicep will make authoring Infrastructure As Code for Azure easier in a near future.