Building a Software Survey using Blazor - Part 10 - Bicep

I've decided to write a small survey site using Blazor. Part of this is an excuse to learn Blazor.

As I learn with Blazor I will blog about it as a series of articles.

This series of articles is not intended to be a training course for Blazor - rather my thought process as I go through learning to use the product.

All articles in this series:


I host my Software Survey site on Azure - not that much of as surprise given the use of both Azure Cosmos DB and Azure SignalR Service.

And I want to document my setup as Infrastructure as Code (IaC) so that it would repeatable and reliable.

Normally I would use the Azure ARM template format.

But given I was already being experimental using Blazor ... why not try something new for the infrastructure as well.

Enter Bicep.

Bicep

Bicep is an open-source project from the Microsoft Azure team - who describe it as:

"Bicep is a Domain Specific Language (DSL) for deploying Azure resources declaratively. It aims to drastically simplify the authoring experience with a cleaner syntax and better support for modularity and code re-use. Bicep is a transparent abstraction over ARM and ARM templates, which means anything that can be done in an ARM Template can be done in bicep" Bicep Github

They also caveat it with:

"Note: Bicep is currently an experimental language and we expect to ship breaking changes in future releases. It is not yet recommended for production usage."

And yet for something very experimental, it really does seem to work very well.

Abstractions over cloud templating languages

I've found ARM templates, like AWS CloudFormation templates, to be very verbose - and can be rather cumbersome to work with.

For AWS work, I've really enjoyed using their Cloud Development Kit as a much better option. The AWS CDK allows you to "develop" your infrastructure using a variety of development languages (although TypeScript is probably the best choice).

For example (taken from AWS Documentation):


import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

export class HelloCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
  }
}

This defines a new CloudFormation stack with an S3 bucket in it.

AWS CDK then will compile this into a standard CloudFormation template.

And at a high level, Azure Bicep operates in much the same way.

It allows you to define your infrastructure in a domain specific language (rather than a general purpose programming language like AWS CDK) and then compiles to an ARM template.

My infrastructure

Here is the Bicep file for a full infrastructure (source)


param environment string = 'e2e'

var nameprefix = 'rfc-${environment}-software-survey'

resource signalr 'Microsoft.SignalRService/SignalR@2020-07-01-preview' = {
    name: '${nameprefix}-signalr'
    location: resourceGroup().location
    kind: 'SignalR'
    sku: {
        name: 'Free_F1'
    }
}

resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2020-06-01-preview' = {
    name: '${nameprefix}-db'
    location: resourceGroup().location
    kind: 'GlobalDocumentDB'
    properties: {
        enableFreeTier: true
        databaseAccountOfferType: 'Standard'
        consistencyPolicy: {
            defaultConsistencyLevel: 'Session'
        }
    }
}

resource applicationInsights 'microsoft.insights/components@2018-05-01-preview' = {
    name: '${nameprefix}-application-insights'
    location: resourceGroup().location
    kind: 'web'
    properties: {
        ApplicationType: 'web'
        publicNetworkAccessForIngestion: 'Enabled'
        publicNetworkAccessForQuery: 'Enabled'
    }
}

resource farm 'Microsoft.Web/serverfarms@2018-11-01' = {
    name: '${nameprefix}-app-plan'
    location: resourceGroup().location
    kind: 'linux'
    sku: {
        name: 'F1'
        capacity: 1
    }
}

resource website 'Microsoft.Web/sites@2018-11-01' = {
    name: '${nameprefix}-web-app'
    location: resourceGroup().location
    kind: 'app'
    properties: {
        serverFarmId: farm.id
        siteConfig: {
            appSettings: [
                {
                    name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
                    value: reference(applicationInsights.id).InstrumentationKey
                }
                {
                    name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
                    value: reference(applicationInsights.id).ConnectionString
                }
                {
                    name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
                    value: '~2'
                }
                {
                    name: 'Azure__SignalR__ConnectionString'
                    value: listKeys(signalr.id, '2020-07-01-preview').primaryConnectionString
                }
                {
                    name: 'Persistance:CosmosDbEndpoint'
                    value: cosmos.properties.documentEndpoint
                }
                {
                    name: 'Persistance:CosmosDbPrimaryKey'
                    value: listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_Mode'
                    value: 'default'
                }
            ]
        }
    }
}

output appServiceName string = website.name
output appServiceUrl string = 'http://${website.name}.azurewebsites.net/'
output cosmosEndpoint string = cosmos.properties.documentEndpoint
output cosmosPrimaryKey string = listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey

In it I:

These 92 lines then get compiled into 129 line ARM template - which can be seen here

Benefits

Mainly, I think that Bicep is considerably more readable than native ARM template.

Bicep is also handling an amount of default values for you.

It is however early days for the tool. As of writing, they have only just moved to Bicep 0.2 - and I've yet to try (there could potentially be breaking changes).

I currently wouldn't recommend for a client's production use - unless they had an appetite for the bleeding edge.

For my purpose however, given it still generated a valid ARM template, I'm more than happy to use it.

A note on Terraform

If you have much experience with IaC, you maybe reading this and asking why I didn't go with Terraform.

To be blunt, I've never really found the opportunity to use Terraform. While I've studied it, my clients are generally working with native template formats.

I would see no problems using Terraform to create the infrastructure.

Outputs

You may have noted that I output a number of values at the end of my script:


output appServiceName string = website.name
output appServiceUrl string = 'http://${website.name}.azurewebsites.net/'
output cosmosEndpoint string = cosmos.properties.documentEndpoint
output cosmosPrimaryKey string = listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey

Those outputs are passed to my End to End tests (introduced in the last article).

Thank you for reading. See you in the next article.

About the author:

Mark Taylor is an experience IT Consultant passionate about helping his clients get better ROI from their Software Development.

He has over 20 years Software Development experience - over 15 of those leading teams. He has experience in a wide variety of technologies and holds certification in Microsoft Development and Scrum.

He operates through Red Folder Consultancy Ltd.