ARM, Azure, Professional

A Real World Translation of ARM to Bicep

This post was written when latest version of bicep was version 0.2.0

Introduction

First apologies for not “flexing” my puns when working on Azure Bicep. However, for those that aren’t aware Azure Bicep is currently an alpha project whose intent is to abstract the JSON ARM template and act as a compiler to assist in creating ARM templates for deployment. Pleas note…YOU STILL NEED TO DEPLOY THE .JSON ARM template. A .bicep file will not deploy directly to Azure at the time of this writing. Rather one would need to build the .bicep(s) file(s) and it will create the .json(s) for deployment.

For those that have some experience with Terraform this sounds slightly familiar. I’ll dive into some of the features and learnings from this but first want to call out what we will walk through resulted in my Azure Quickstart Template for Web App w/ Application Insights sending to Log Analytics and translating that exact same ARM template into a Bicep Example: 201-web-app-loganalytics

The pros of doing this is the Azure Quickstart templates runs through the arm-ttk to ensure best practices are being adhered to. This serves as a good “baseline” for some “recommended practices”. Apologies for all the “quotes” but hopefully it’s understood the intention.

Starting in bicep

First to setup and configure I followed this walkthrough from Build5Nines It did a good job getting you into the language, in addition the ReadMe on the Bicep github is a valuable resource.

After getting setup I confirmed I was able to deploy a simple storage account like some of the walkthroughs out there have already provided.

Some of the quick first impressions include:

  • Wait no commas?
  • No "" around everything
  • Holy IntelliSense
  • Little easier to keep track of all {}‘s

To start it was pretty straight forward to take the parameters originally defined in the azuredeploy.json. Process was pretty straightforward and those with some development experience may find it easier to take:

    "skuName": {
      "type": "string",
      "defaultValue": "S1"
    },

and translate it to:

param skuName string = 'S1'

essentially we just condensed four lines of code into one for setting a default parameter. These parameters may also be fed in from a separate file.

Something to take note of too is when we start using ARM functions like:

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

they translate into:

param location string = resourceGroup().location

Notice, we didn’t have to escape with [] our bicep code just recognize the command resourceGroup() natively!

The next section variables[] was also pretty straight forward:

 "appServicePlanName": "[toLower(concat('asp-', parameters('appName')))]"

to

var appServicePlanName = toLower('asp-${appName}')

The key takeaway here is we didn’t have to define a variables[] section as well aslo look at the concatination….we didn’t need to call the concat function as well as look at how we are referencing our parameters. No longer is parameters() needed. Rather this has been replaced with the '${}' which better aligns with some other programming languages.

Now for the bread and butter….how resources are defined! If you have experience with ARM then you’ve struggled and felt some of the pain of defining all the resources in the resource[] section with all of it’s dependencies and with all of it’s segments. Spoiler this is easier; however, there still are a kink or two to flush out at the time of this writing.

In bicep each resource is given it’s own module. Yes, if you are familiar with Terraform this module is the same concept.

Essentially in ARM our resource type and API version are now declared in one line and we will give our resource/module a name to which we can reference in additional resources. For this specific example let’s look at the appServicePlan definition. In Bicep we have it defined as:

resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
....
}

So our bicep will recognize this module as being called appServicePlan and we can use this name when being referenced by additional modules. For those familar with App Service Architecture we will need to know our App Service Plan in order to host our App Service on. For those unfamilar with Azure Web Technologies think of App Service Plan = IIS Server and App Service = Site.

For our reference in the App Service to App Service Plan in bicep we state it as:

serverFarmId: appServicePlan.id

this replaces the ARM version of this reference:

"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"

Also just as cool and perhaps even more important bicep is smart enough to know that our App Service has a reference to something in the appServicePlan module so it now knows to dynamically create the dependsOn. For those unfamilar dependsOn in ARM is the way we tell Azure don’t deploy resource A until resource B is deployed. With bicep this dependency was automagically created for us in our ARM output thus saving us some time and headache.

Advanced Usage

The grass isn’t all green though. There are still a few kinks to work out. One of the first headaches I had was getting warnings and issues when working with resources with multiple segments. Similar to this one, let’s looks specifically at the “siteextensions”.

In the ARM template this is defined as a subresource inside our App Service (Microsoft.Web/sites) resource type like:

{
            "type": "Microsoft.Web/sites",
            "apiVersion": "2019-08-01",
            "name": "[variables('webSiteName')]",
            "location": "[parameters('location')]",
            .....
            "resources": [
                 {
                    "type": "siteextensions",
                    "apiVersion": "2019-08-01",
                    "name": "Microsoft.ApplicationInsights.AzureWebSites",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/Sites', variables('WebsiteName'))]",
                        "[resourceId('microsoft.insights/components', variables('appInsightName'))]"
                    ]
                }
}
         

I suppose I could have done this again in bicep…but why? Isn’t the point to help break down components to be a little more modular?

resource appServiceSiteExtension 'Microsoft.Web/sites/siteextensions@2020-06-01' = {
  name: '${appService.name}/Microsoft.ApplicationInsights.AzureWebsites'
  dependsOn: [
    appInsights
  ]
}

So when first putting in this in I notice I get a warning for:

Resource type "Microsoft.Web/sites/siteextensions@2020-06-01" does not have types available.

What this leads to is then IntelliSense is broken too inside this resource. There is a bug open for this in the .bicep backlog

Alright….so now I need to fly a little blind..luckily this type isn’t to complicated. First I just put like we did in ARM:

name: 'Microsoft.ApplicationInsights.AzureWebSites'

Alas did not work as we get an all too familar error

Deployment template validation failed: 'The template resource {resource-name}'
for type {resource-type} has incorrect segment lengths.

Well working with ARM for a file I realized that if this truly is 100% compatible decided to try:

name: '${appService.name}/Microsoft.ApplicationInsights.AzureWebsites'

And WOW it worked! Now, next up and this just comes from logic and to be fair I haven’t tested both ways I realized that the site extension for app insights now refers to the appService segment, this via referencing the name property. This still won’t solve all the dependsOn….we still will require our Application Insights module to complete. To help satisfy this needed to include:

  dependsOn: [
    appInsights
  ]

What this does is saying our module appServiceSiteExtension won’t run until the module appInsights completes. Now this is a good foray into another stumbling block I had to converting this template….how the heck to reference the InstrumentationKey from Application Insights in the App Service configuration. All the documentation and references seem to only go one layer down. aka how to get a name, resource,ID, etc…but how to get the properties. Well if troubleshooting ARM/JSON in the past we can identify that the InstrumentationKey is part of the properties via Powershell

Get-AzApplicationInsights -ResourceGroupName "testgroup" -Name "test"

Id                 : /subscriptions/{subid}/resourceGroups/testgroup/providers/microsoft.insights/components/test
ResourceGroupName  : testgroup
Name               : test
Kind               : web
Location           : eastus
Type               : microsoft.insights/components
AppId              : 7c8f0641-d307-41bc-b8f2-d30701adb4b3
ApplicationType    : web
Tags               : {}
CreationDate       : 7/5/2017 4:37:22 PM
FlowType           : Redfield
HockeyAppId        :
HockeyAppToken     :
InstrumentationKey : 1e30d092-4b4b-47c6-ad39-7c10785d80f5
ProvisioningState  : Succeeded
RequestSource      : IbizaAIExtension
SamplingPercentage :
TenantId           : b90b0dec-9b9a-4778-a84e-4ffb73bb17f7

So it makes sense then to try:

appInsights.properties.InstrumentationKey

Low and behold it works!

Committing to the bicep Project

Next up once I had this working and proved to myself the compiled JSON deployed correctly was how to get this example out to others who might be interested in learning bicep.

I started pretty straightforward and forked the bicep repo. Then followed their contributing guide. I followed all the steps to make sure the project, in my case the playground, fully worked. Then I followed the outline on how to contribute to the example files. Essentially it was:

  • Create the appropriate folders under the examples folder
  • add my .bicep and .json files
  • Update the src/examples.ts to import the newly defined .bicep file and then add it to the examples object with the name you’d like it to appear in the playground.

Note, not everyone does that last step so the “real” list of examples is in here as opposed to the playground.

Now when creating my examples I did get fail checks due to the error mentioned about

Resource type "Microsoft.Web/sites/siteextensions@2020-06-01" does not have types available.

I had someone from the project quickly reach out to me and inform me to solve this to add the resource type to the permittedMissingTypeDisgnostics block of the file in src\Bicep.Core.Samples\ExamplesTests.cs

After adding my resource types there the tests pass and I was able to merge in my code after approval.

Overall Impression

Seems like bicep does a great of job of putting the CODE feeling in Infrastructure as CODE. Having worked with ARM templates for an extended period of time I feel the similarities are there and can quickly transpose one to the other. For the record writing this blog post actually has taken me longer than it took to transpose my quickstart tempalate from native ARM to bicep.

Professional opinion is bicep is a growing tool one that I feel will most likely get incorporated in future Microsoft technologies and excited to see what is next. I will say it is a very active code base. I’ve submitted now a couple PRs and comments and normally hear back within an hour or two.

Follow Up

Check out my post on how to use modules with biceps