DEV Community

Cover image for First look at Project Bicep, an Azure Resource Manager templates DSL
Olivier Miossec
Olivier Miossec

Posted on

First look at Project Bicep, an Azure Resource Manager templates DSL

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

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

Bicep build test.bicep
Enter fullscreen mode Exit fullscreen mode

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

param name type
Enter fullscreen mode Exit fullscreen mode

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.

param localAdminPassword string {
    secure: true
}
Enter fullscreen mode Exit fullscreen mode

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.

param vmPrefix string {
    minLength: 1
    maxLength: 9
}
Enter fullscreen mode Exit fullscreen mode

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

To declare a variable:

var defaultLocation = 'francecentral'
Enter fullscreen mode Exit fullscreen mode

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

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

param vmPrefix string {
    minLength: 1
    maxLength: 9
}

var defaultVmName = '${vmPrefix}-${environmentName}'
Enter fullscreen mode Exit fullscreen mode

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

var VmName = concat('${vmPrefix}','-','${environmentName}')
Enter fullscreen mode Exit fullscreen mode

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()

var diskSku = environmentName == 'prod' ? 'Premium_LRS' : 'Standard_LRS'
Enter fullscreen mode Exit fullscreen mode

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.

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

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.

        dataDisks: [
          {
            name: '${defaultVmName}-vhd'
            createOption: 'attach'
            caching: 'ReadOnly'
            lun: 0
            managedDisk: {
              id: vmDataDisk.id
            }
          }
        ]
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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

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.

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

      ]
    }
  }
Enter fullscreen mode Exit fullscreen mode

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.

Bicep build vm.bicep
Enter fullscreen mode Exit fullscreen mode

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) and there some now 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.

Discussion (6)

Collapse
mertsenel profile image
MertSenel • Edited

We have discussed this tool last week at work.
Currently copy and conditional resources are not supported which is very critical for us.

Too much way to go. Code looks cleaner now, but ARM has a pretty good intellisense vscode extension already, coupled with the json schema assistance. The biggest hurdle with ARM is the reusability/modularity of the code and nested & linked templates are not so easy to use without the boilerplate extra syntax and artifacts upload.

Any tooling is welcome in my opinion, and curious to see their solution about the above topic.

Collapse
omiossec profile image
Olivier Miossec Author

Hi
Bicep is still in alpha
Module and intellisense should come in next week's
Loops and conditions will follow in 2021 Q1

Collapse
omiossec profile image
Olivier Miossec Author

Visual Studio Code Intellisense support October
Module November
Loops and condition in December/January

Thread Thread
mertsenel profile image
MertSenel

Thanks for the information

Collapse
steffanrostvig profile image
SteffanR • Edited

Just the info I was looking for. This helped me adding a datadisk to a deployment. Thanks! 👍👏

Collapse
antoinega profile image
Antoine

Thanks for the post!