In the previous post, I introduced the very early stage of Project Bicep. At that time, it was the version of 0.1.x
, but now it's updated to 0.3.x
. You can use it for production, and many features keep being introduced. Throughout this post, I'm going to discuss new features added since the last post.
- Project Bicep Sneak Peek
- GitHub Actions and ARM Template Toolkit for Bicep Codes Linting
- Azure Bicep Refreshed
Azure CLI Integration
While Bicep CLI works as a stand-alone tool, it's been integrated with Azure CLI from v2.20.0 and later. Therefore, you can run bicep in either way.
# Bicep CLI | |
bicep build azuredeploy.bicep | |
# Azure CLI | |
az bicep build --file azuredeploy.bicep |
NTOE: Although Bicep CLI could build multiple files by
v0.2.x
, it's now only able to build one file at a time fromv0.3.x
. Therefore, if you want to build multiple files, you should do it differently. Here's a sample PowerShell script, for example.This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Get-ChildItem -Path **/*.bicep -Recurse | ForEach-Object { az bicep build --file $_.FullName }
Because of the Azure CLI integration, you can also provision resources through the bicep file like below:
# ARM template file | |
az deployment group create \ | |
--name <deployment_name> \ | |
--resource-group <resource_group_name> \ | |
--template-file azuredeploy.json \ | |
--parameters @azuredeploy.parameters.json | |
# Bicep file | |
az deployment group create \ | |
--name <deployment_name> \ | |
--resource-group <resource_group_name> \ | |
--template-file azuredeploy.bicep \ | |
--parameters @azuredeploy.parameters.json |
Bicep Decompiling
From v0.2.59
, Bicep CLI can convert ARM templates to bicep files. It's particularly important because still many ARM templates out there have been running and need maintenance. Run the following command for decompiling.
# Bicep CLI | |
bicep decompile azuredeploy.json | |
# Azure CLI | |
az bicep decompile --file azuredeploy.json |
NOTE: If your ARM template contains a
copy
attribute, bicep can't decompile it as of this writing. In the later version, it should be possible.
Decorators on Parameters
Writing parameters has become more articulate than v0.1.x
, using the decorators. For example, there are only several possible SKU values of a Storage Account available, so using the @allowed
decorator like below makes the code better readability.
// Without decorators | |
param storageAccountSku string { | |
allowd: [ | |
'Standard_GRS' | |
'Standard_LRS' | |
] | |
default: 'Standard_LRS' | |
} | |
// With decorators | |
@allowed([ | |
'Standard_GRS' | |
'Standard_LRS' | |
]) | |
param storageAccountSku string = 'Standard_LRS' |
Conditional Resources
You can use ternary operations for attributes. What if you can conditionally declare a resource itself using conditions? Let's have a look. The following code says if the location is only Korea Central, the Azure App Service resource can be provisioned.
param location = resourceGroup().location | |
resource webapp 'Microsoft.Web/sites@2020-12-01' = if (location == 'koreacentral') { | |
... | |
} |
Loops
While ARM templates use both copy
attribute and copyIndex()
function for iterations, bicep uses the for...in
loop. Have a look at the code below. You can declare Azure App Service instances using the array parameter through the for...in
loop.
param webapps array = [ | |
'dev' | |
'test' | |
'prod' | |
] | |
// Use array only | |
resource webapp 'Microsoft.Web/sites@2020-12-01' = [for name in webapps: { | |
name: 'my-webapp-${name}' | |
... | |
}] |
You can also use both array and index at the same time.
// Use both array and index | |
resource webapp 'Microsoft.Web/sites@2020-12-01' = [for (name, index) in webapps: { | |
name: 'my-webapp-${name}-${index + 1}' | |
... | |
}] |
Instead of the array, you can use the range()
function in the loop.
// Use range | |
resource webapp 'Microsoft.Web/sites@2020-12-01' = [for i in range(0, 10): { | |
name: 'my-webapp-${index + 1}' | |
... | |
}] |
Please note that you MUST use the array expression ([...]
) outside the for...in
loop because it declares the array of the resources. Bicep will do the rest.
Modules
Personally, I love this part. While ARM templates use the linked template, bicep uses the module
keyword for modularisation. Here's the example for Azure Function app provisioning. For this, you need at least Storage Account, Consumption Plan and Azure Functions resources. Each resource can be individually declared as a module, and the orchestration bicep file calls each module. Each module should work independently, of course.
Storage Account
// storageAccount.bicep | |
param resourceName string | |
param location string = resourceGroup().location | |
resource st 'Microsoft.Storage/storageAccounts@2021-02-01' = { | |
name: resourceName | |
location: location | |
... | |
} | |
output id string = st.id | |
output name string = st.name |
Consumption Plan
// consumptionPlan.bicep | |
param resourceName string | |
param location string = resourceGroup().location | |
resource csplan 'Microsoft.Web/serverfarms@2020-12-01' = { | |
name: resourceName | |
location: location | |
... | |
} | |
output id string = csplan.id | |
output name string = csplan.name |
Azure Functions
// functionApp.bicep | |
param resourceName string | |
param location string = resourceGroup().location | |
param storageAccountId string | |
param storageAccountName string | |
param consumptionPlanId string | |
resource fncapp 'Microsoft.Web/sites@2020-12-01' = { | |
name: resourceName | |
location: location | |
... | |
properties: { | |
serverFarmId: consumptionPlanId | |
... | |
siteConfig: { | |
appSettings: [ | |
{ | |
name: 'AzureWebJobsStorage' | |
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccountId, '2021-02-01').keys[0].value}' | |
} | |
... | |
] | |
} | |
} | |
} | |
output id string = fncapp.id | |
output name string = fncapp.name |
Modules Orchestration
Here's the orchestration bicep file to combine modules. All you need to do is to declare a module, refer to the module location and pass parameters. Based on the references between modules, dependencies are automatically calculated.
// azuredeploy.bicep | |
param resourceName string | |
param location string = resourceGroup().location | |
module st './storage-account.bicep' = { | |
name: 'StorageAccountProvisioning' | |
params: { | |
name: resourceName | |
location: location | |
} | |
} | |
module csplan './consumption-plan.bicep' = { | |
name: 'ConsumptionPlanProvisioning' | |
params: { | |
name: resourceName | |
location: location | |
} | |
} | |
module fncapp './function-app.bicep' = { | |
name: 'FunctionAppProvisioning' | |
params: { | |
name: resourceName | |
location: location | |
storageAccountId: st.outputs.id | |
storageAccountName: st.outputs.name | |
consumptionPlanId: csplan.outputs.id | |
} | |
} |
NOTE: Unfortunately, as of this writing, referencing to external URL is not supported yet, unlike linked ARM templates.
So far, we've briefly looked at the new features of Project Bicep. As Bicep is one of the most rapidly growing toolsets in Azure, keep using it for your resource management.