loading...
Cover image for First look at Project Bicep, an Azure Resource Manager templates DSL

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

omiossec profile image Olivier Miossec ・6 min read

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

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

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
}

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
}

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

To declare a variable:

var defaultLocation = 'francecentral'

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

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

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

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.

  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.

        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.

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

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'))]"
      ]

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

      ]
    }
  }

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

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.

Posted on by:

omiossec profile

Olivier Miossec

@omiossec

Microsoft Azure MVP, Passionate about Cloud and DevOps. Co-organizers of the French PowerShell & DevOps UG . Find me on https://github.com/omiossec

Discussion

pic
Editor guide
 

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.

 

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

 

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

Thanks for the information

 

Thanks for the post!