Friday 11 September 2020

Azure Bicep

Azure Bicep is an open source project for deploying Azure resources declaratively. It is an Azure Resource Manager (ARM) Domain Specific Language (DSL). Its main purpose is to simplify the authoring of ARM templates with a cleaner syntax and be easier to understand.

 

The way you compile Bicep code is by using the Bicep CLI. A Bicep VS Code extension is also available to author the Bicep code. A Bicep file has a .bicep extension.

 

You can install Bicep on Windows by running the following PowerShell script.

 

$installPath = "$env:USERPROFILE\.bicep"

$installDir = New-Item -ItemType Directory -Path $installPath -Force

$installDir.Attributes += 'Hidden'

(New-Object Net.WebClient).DownloadFile("https://github.com/Azure/bicep/releases/latest/download/bicep-win-x64.exe""$installPath\bicep.exe")

$currentPath = (Get-Item -path "HKCU:\Environment" ).GetValue('Path''''DoNotExpandEnvironmentNames')

if (-not $currentPath.Contains("%USERPROFILE%\.bicep")) { setx PATH ($currentPath + ";%USERPROFILE%\.bicep") }

if (-not $env:path.Contains($installPath)) { $env:path += ";$installPath" }

bicep --help

 

And you can install the VS Code extension by running the following PowerShell script.

 

$vsixPath = "$env:TEMP\vscode-bicep.vsix"

(New-Object Net.WebClient).DownloadFile("https://github.com/Azure/bicep/releases/latest/download/vscode-bicep.vsix"$vsixPath)

code --install-extension $vsixPath

Remove-Item $vsixPath

 

For Mac or Linux visit https://github.com/Azure/bicep/blob/master/docs/installing.md

 

OK now that you have it installed let's dig into some Bicep code. The example below generates an ARM template to create a storage account with a unique name, provides the option for LRS or GRS, creates a container and output some values. This is a more complex example so that I can demonstrate how it matches the ARM template parameters, variables and functions.

 

param location string = resourceGroup().location

param namePrefix string = 'sa'

param globalRedundancy bool = true

var storageAccountName = '${namePrefix}${uniqueString(resourceGroup().id)}'

var storageSku = 'Standard_LRS'

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {

    namestorageAccountName

    locationlocation

    kind'Storage'

    sku: {

        nameglobalRedundancy ? 'Standard_GRS' : 'Standard_LRS'

    }

}

resource blob 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = {

    name'${storageAccount.name}/static'

}

  

output storageId string = storageAccount.id

output computedStorageName string = storageAccount.name

output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blob

 

OK so let's break it down. Note that I won’t cover every single line, only the ones which need to be understood.

 

First we define a parameter and assign the resource group's location value.

 

param location string = resourceGroup().location

 

In JSON this would be equivalent to:

 

  "parameters": {

    "location": {

      "type""string",

      "defaultValue""[resourceGroup().location]"

    }

    }

 

Then we assign a value to the storageAccountName variable based on the value of the namePrefix variable concatenated with a unique string based on the resource group id. Note the use of interpolation ${}${} which is much simpler than using concat or format in JSON.

 

var storageAccountName = '${namePrefix}${uniqueString(resourceGroup().id)}'

 

In JSON this would be equivalent to:

 

  "variables": {

    "storageAccountName""[format('{0}{1}', parameters('namePrefix'), uniqueString(resourceGroup().id))]"

  }

 

Then we declare the storage account. Note that we give it a name of storageAccount. This is not the storage account name, this is a name which we can reference the object later. The storage account name is specified inside the {}. Then we specify the 'Resource Provider\Resource Type@API version'. Also note the use of the ternary operator ? to specify a condition which is equivalent to if true then Standard_GRS else Standard_LRS.

 

resource storageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = {

    namestorageAccountName

    locationlocation

    kind'Storage'

    sku: {

        nameglobalRedundancy ? 'Standard_GRS' : 'Standard_LRS'

    }

}

 

In JSON this would be equivalent to:

 

  "resources": [

    {

      "type""Microsoft.Storage/storageAccounts",

      "apiVersion""2019-06-01",

      "name""[variables('storageAccountName')]",

      "location""[parameters('location')]",

      "kind""Storage",

      "sku": {

        "name""[if(parameters('globalRedundancy'), 'Standard_GRS', 'Standard_LRS')]"

      }

    }

 

Then we declare a new container which when compiled will automatically add the dependsOn property because we are referencing a property of the resource via the symbolic name. On ARM templates you must manually declare the dependencies.

 

resource blob 'Microsoft.Storage/storageAccounts/blobServices/containers@2019-06-01' = {

    name'${storageAccount.name}/static'

}

 

In JSON this would be equivalent to:

 

    {

      "type""Microsoft.Storage/storageAccounts/blobServices/containers",

      "apiVersion""2019-06-01",

      "name""[format('{0}/default/logs', variables('storageAccountName'))]",

      "dependsOn": [

        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"

      ]

    }

 

And finally we have the outputs. The first line demonstrates a shortcut for the resourceId property, the second line demonstrates calling a property of the storage account using the symbolic name and the last line demonstrates what would be equivalent to the reference function in the ARM template.

 

output storageId string = storageAccount.id

output computedStorageName string = storageAccount.name

output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blob

 

In JSON this would be equivalent to:

 

  "outputs": {

    "storageId": {

      "type""string",

      "value""[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"

    },

    "computedStorageName": {

      "type""string",

      "value""[variables('storageAccountName')]"

    },

    "primaryEndpoint": {

      "type""string",

      "value""[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))).primaryEndpoints.blob]"

    }

  }

 

You can use the Bicep Playground to play around with Bicep code and see it translated to JSON in an ARM template.

 

Once you have your code sorted, you can compile it running the following command which will generate a JSON file.

 

bicep build ./storage.bicep

 

Then you can deploy it as you would normally do.

 

New-AzResourceGroup -Name storageAccounts -Location australiaeast

New-AzResourceGroupDeployment -TemplateFile ./storage.json -ResourceGroupName storageAccounts

 

If you have already authored many ARM templates, don't worry as a decompiler is in the roadmap to allow the templates to be translated into Bicep code.

 

To learn more visit https://github.com/Azure/bicep

No comments:

Post a Comment