Skip to main content

3 posts tagged with "bicep"

View All Tags

Hello World Bicep

Bicep makes Azure Resource Management a great deal simpler than ARM templates. The selling point here is grokkability. This post takes a look at the "Hello World" example recently added to the Bicep repo to appreciate quite what a difference it makes.

hello world bicep

More than configuration#

The "Hello World" added to the Bicep repo by Chris Lewis illustrates the simplest usage of Bicep:

This bicep file takes a yourName parameter and adds that to a hello variable and returns the concatenated string as an ARM output.

This is, when you consider it, the very essence of a computer program. Taking an input, doing some computation and providing an output. When I think about ARM templates, (and because Bicep is transpiled into ARM templates I mentally bracket the two together) I tend to think about resources being deployed. I focus on configuration, not computation

This is an imperfect mental model. ARM templates can do so much more than deploy by slinging strings and numbers. Thanks to the wealth of template functions that exist they have much more power. They can do computation.

The Hello World example focuses just on computation.

From terse to verbose#

The Hello World example is made up of two significant files:

  1. main.bicep - the bicep code
  2. main.json - the ARM template compiled from the Bicep file

The main.bicep file amounts to 3 lines of code (I have omitted the comment line):

param yourName string
var hello = 'Hello World! - Hi'
output helloWorld string = '${hello} ${yourName}'
  • the first line takes the input of yourName
  • the second line declares a hello variable
  • the third line computes the new value of helloWorld based upon hello and yourName, then passes it as output

Gosh is it ever simple. It's easy to read and it's simple to understand. Even if you don't know Bicep, if you've experience in another language you can likely guess what's happening.

Let's compare this with the main.json that main.bicep is transpiled into:

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "6989941473549654446"
}
},
"parameters": {
"yourName": {
"type": "string"
}
},
"functions": [],
"variables": {
"hello": "Hello World! - Hi"
},
"resources": [],
"outputs": {
"helloWorld": {
"type": "string",
"value": "[format('{0} {1}', variables('hello'), parameters('yourName'))]"
}
}
}

The above ARM template expresses exactly the same thing as the Bicep alternative. But that 3 lines of logic has become 27 lines of JSON. We've lost something in the transition. Intent is no longer clear. We've gone from something easy to reason about, to something that is hard to reason about. You need to think a lot less to write the Bicep alternative and that's a good thing.

I was chatting to someone recently who expressed it well by saying:

ARM is the format that the resource providers understand, so really it’s the Azure equivalent of Assembler – and I don’t know anyone who enjoys coding in Assembler.

This is a great example of the value that Bicep provides. If you'd like to play with the Hello World a little, why not take it for a spin in the Bicep playground.

Bicep meet Azure Pipelines 2

Last time I wrote about how to use the Azure CLI to run Bicep within the context of an Azure Pipeline. The solution was relatively straightforward, and involved using az deployment group create in a task. There's an easier way.

Bicep meet Azure Pipelines

The easier way#

The target reader of the previous post was someone who was already using [email protected] in an Azure Pipeline to deploy an ARM template. Rather than replacing your existing [email protected] tasks, all you need do is insert a prior bash step that compiles the Bicep to ARM, which your existing template can then process. It looks like this:

- bash: az bicep build --files infra/app-service/azuredeploy.bicep
displayName: "Compile Bicep to ARM"

This will take your Bicep template of azuredeploy.bicep, transpile it into an ARM template named azuredeploy.json which a subsequent [email protected] task can process. Since this is just exercising the Azure CLI, using bash is not required; powershell etc would also be fine; it's just required that the Azure CLI is available in a pipeline.

In fact this simple task could even be a one-liner if you didn't fancy using the displayName. (Though I say keep it; optimising for readability is generally a good shout.) A full pipeline could look like this:

- bash: az bicep build --files infra/app-service/azuredeploy.bicep
displayName: "Compile Bicep to ARM"
displayName: "Deploy Hello Azure ARM"
inputs:
azureResourceManagerConnection: '$(azureSubscription)'
action: Create Or Update Resource Group
resourceGroupName: '$(resourceGroupName)'
location: 'North Europe'
templateLocation: Linked artifact
csmFile: 'infra/app-service/azuredeploy.json' # created by bash script
csmParametersFile: 'infra/app-service/azuredeploy.parameters.json'
deploymentMode: Incremental
deploymentOutputs: resourceGroupDeploymentOutputs
overrideParameters: -applicationName $(Build.Repository.Name)
- pwsh: |
$outputs = ConvertFrom-Json '$(resourceGroupDeploymentOutputs)'
foreach ($output in $outputs.PSObject.Properties) {
Write-Host "##vso[task.setvariable variable=RGDO_$($output.Name)]$($output.Value.value)"
}
displayName: "Turn ARM outputs into variables"

And when it's run, it may result in something along these lines:

Bicep in an Azure Pipeline

So if you want to get using Bicep right now with minimal effort, this an on ramp that could work for you! Props to Jamie McCrindle for suggesting this.

Bicep meet Azure Pipelines

Bicep is a terser and more readable alternative language to ARM templates. Running ARM templates in Azure Pipelines is straightforward. However, there isn't yet a first class experience for running Bicep in Azure Pipelines. This post demonstrates an approach that can be used until a Bicep task is available.

Bicep meet Azure Pipelines

Bicep: mostly ARMless#

If you've been working with Azure and infrastructure as code, you'll likely have encountered ARM templates. They're a domain specific language that lives inside JSON, used to define the infrastructure that is deployed to Azure; App Services, Key Vaults and the like.

ARM templates are quite verbose and not the easiest thing to read. This is a consequence of being effectively a language nestled inside another language. Bicep is an alternative language which is far more readable. Bicep transpiles down to ARM templates, in the same way that TypeScript transpiles down to JavaScript.

Bicep is quite new, but already it enjoys feature parity with ARM templates (as of v0.3) and ships as part of the Azure CLI. However, as Bicep is new, it doesn't yet have a dedicated Azure Pipelines task for deployment. This should exist in future, perhaps as soon as the v0.4 release. In the meantime there's an alternative way to achieve this which we'll go through.

App Service with Bicep#

Let's take a simple Bicep file, azuredeploy.bicep, which is designed to deploy an App Service resource to Azure. It looks like this:

@description('Tags that our resources need')
param tags object = {
costCenter: 'todo: replace'
environment: 'todo: replace'
application: 'todo: replace with app name'
description: 'todo: replace'
managedBy: 'ARM'
}
@minLength(2)
@description('Base name of the resource such as web app name and app service plan')
param applicationName string
@description('Location for all resources.')
param location string = resourceGroup().location
@description('The SKU of App Service Plan')
param sku string
var appServicePlanName_var = 'plan-${applicationName}-${tags.environment}'
var linuxFxVersion = 'DOTNETCORE|5.0'
var fullApplicationName_var = 'app-${applicationName}-${uniqueString(applicationName)}'
resource appServicePlanName 'Microsoft.Web/[email protected]' = {
name: appServicePlanName_var
location: location
sku: {
name: sku
}
kind: 'linux'
tags: {
CostCenter: tags.costCenter
Environment: tags.environment
Description: tags.description
ManagedBy: tags.managedBy
}
properties: {
reserved: true
}
}
resource fullApplicationName 'Microsoft.Web/[email protected]' = {
name: fullApplicationName_var
location: location
kind: 'app'
tags: {
CostCenter: tags.costCenter
Environment: tags.environment
Description: tags.description
ManagedBy: tags.managedBy
}
properties: {
serverFarmId: appServicePlanName.id
clientAffinityEnabled: true
siteConfig: {
appSettings: []
linuxFxVersion: linuxFxVersion
alwaysOn: false
ftpsState: 'Disabled'
http20Enabled: true
minTlsVersion: '1.2'
remoteDebuggingEnabled: false
}
httpsOnly: true
}
identity: {
type: 'SystemAssigned'
}
}
output fullApplicationName string = fullApplicationName_var

When transpiled down to an ARM template, this Bicep file more than doubles in size:

  • azuredeploy.bicep - 1782 bytes
  • azuredeploy.json - 3863 bytes

This tells you something of the advantage of Bicep. The template comes with an associated azuredeploy.parameters.json file:

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"tags": {
"value": {
"costCenter": "8888",
"environment": "stg",
"application": "hello-azure",
"description": "App Service for hello-azure",
"managedBy": "ARM"
}
},
"sku": {
"value": "B1"
}
}
}

It's worth remembering that you can use the same parameters files with Bicep that you can use with ARM templates. This is great for minimising friction when it comes to migrating.

Bicep in azure-pipelines.yml#

Now we have our Bicep file, we want to execute it from the context of an Azure Pipeline. If we were working directly with the ARM template we'd likely have something like this in place:

displayName: "Deploy Hello Azure ARM"
inputs:
azureResourceManagerConnection: '$(azureSubscription)'
action: Create Or Update Resource Group
resourceGroupName: '$(resourceGroupName)'
location: 'North Europe'
templateLocation: Linked artifact
csmFile: 'infra/app-service/azuredeploy.json'
csmParametersFile: 'infra/app-service/azuredeploy.parameters.json'
deploymentMode: Incremental
deploymentOutputs: resourceGroupDeploymentOutputs
overrideParameters: -applicationName $(Build.Repository.Name)
- pwsh: |
$outputs = ConvertFrom-Json '$(resourceGroupDeploymentOutputs)'
foreach ($output in $outputs.PSObject.Properties) {
Write-Host "##vso[task.setvariable variable=RGDO_$($output.Name)]$($output.Value.value)"
}
displayName: "Turn ARM outputs into variables"

There's two tasks above. The first is the native task for ARM deployments which takes our ARM template and our parameters and deploys them. The second task takes the output variables from the first task and converts them into Azure Pipeline variables such that they can be referenced later in the pipeline. In this case this variablifies our fullApplicationName output.

There is, as yet, no [email protected]. Though it's coming. In the meantime, the marvellous Alex Frankel advised:

I'd recommend using the Azure CLI task to deploy. As long as that task is updated to Az CLI version 2.20 or later, it will automatically install the bicep CLI when calling az deployment group create -f main.bicep.

Let's give it a go!

displayName: "Deploy Hello Azure Bicep"
inputs:
azureSubscription: '$(azureSubscription)'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az --version
echo "az deployment group create --resource-group '$(resourceGroupName)' --name appservicedeploy"
az deployment group create --resource-group '$(resourceGroupName)' --name appservicedeploy \
--template-file infra/app-service/azuredeploy.bicep \
--parameters infra/app-service/azuredeploy.parameters.json \
--parameters applicationName='$(Build.Repository.Name)'
echo "az deployment group show --resource-group '$(resourceGroupName)' --name appservicedeploy"
deploymentoutputs=$(az deployment group show --resource-group '$(resourceGroupName)' --name appservicedeploy \
--query properties.outputs)
echo 'convert outputs to variables'
echo $deploymentoutputs | jq -c '. | to_entries[] | [.key, .value.value]' |
while IFS=$"\n" read -r c; do
outputname=$(echo "$c" | jq -r '.[0]')
outputvalue=$(echo "$c" | jq -r '.[1]')
echo "setting variable RGDO_$outputname=$outputvalue"
echo "##vso[task.setvariable variable=RGDO_$outputname]$outputvalue"
done

The above is just a single Azure CLI task (as advised). It invokes az deployment group create passing the relevant parameters. It then acquires the output properties using az deployment group show. Finally it once again converts these outputs to Azure Pipeline variables with some jq smarts.

This works right now, and running it results in something like the output below. So if you're excited about Bicep and don't want to wait for 0.4 to start moving on this, then this can get you going. To track the progress of the custom task, keep an eye on this issue.

Bicep in an Azure Pipeline

Update: an even simpler alternative#

There is even a simpler way to do this which I discovered subsequent to writing this. Have a read.