5 min read

Mastering Bicep Documentation: Annotate Your Scripts for Seamless Collaboration

Learn how to use decorators and metadata in Bicep to create clear, informative documentation for your infrastructure-as-code scripts. Enhance collaboration and knowledge sharing.
Mastering Bicep Documentation: Annotate Your Scripts for Seamless Collaboration

Bicep allows you to add decorators to describe and enforce the correct usage of modules. In addition, it will enable you to add metadata to enhance the documentation of the infrastructure as code delivery. You can use both sources to create automatically Markdown documentation to have a handy reference for future users and developers of your scripts.

How to Annotate a Bicep File

There are two ways to include annotations in Bicep

  1. Using Decorators
  2. Using Metadata

Decorators

Decorators are used during execution to describe parameters and enforce the correctness of passed values.

A simple example will showcase this. Let's use this resource definition for a storage module.

param storageAccountName string
param skuName string = 'Standard_LRS'
param location string = resourceGroup().location

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
}

Simple Storage Module without Annotations

Let's focus on the skuName parameter. t is a string and has a default value. If you pass in a value like something which is not valid at all, you will not spot the error until deployment. Even az deployment group validate and a what-if analysis will not spot the error.

❯ az deployment group what-if --template-file .\base\storage.bicep --resource-group rg-demo --parameters skuName=something --parameters storageAccountName=289jijdj3ei

Resource and property changes are indicated with this symbol:
  + Create

The deployment will update the following scope:

Scope: /subscriptions/___/resourceGroups/rg-demo

  + Microsoft.Storage/storageAccounts/289jijdj3ei [2023-01-01]

      apiVersion: "2023-01-01"
      id:         "/subscriptions/___/resourceGroups/rg-demo/providers/Microsoft.Storage/storageAccounts/289jijdj3ei"
      kind:       "StorageV2"
      location:   "northeurope"
      name:       "289jijdj3ei"
      sku.name:   "something"
      type:       "Microsoft.Storage/storageAccounts"

Resource changes: 1 to create.

az deployment group what-if doesn't care about the wrong value

If we modify the corresponding bicep file with decorators, we can enforce an error using the analysis tools. Here is a modified version of the same storage module, now using decorators to describe expected parameter behavior in addition to descriptions.

@minLength(3)
@maxLength(24)
@description('Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.')
param storageAccountName string

@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Standard_ZRS'
  'Premium_LRS'
])
@description('Please talk with the subscription owner if a different SKU is needed')
param skuName string = 'Standard_LRS'

param location string = resourceGroup().location

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
}

Storage module with decorators

Now that the what-if analysis is being executed, the what-if analysis will correctly interrupt with the error message of the wrong value in the parameter skuName.

❯ az deployment group what-if --template-file .\base\storage.bicep --resource-group rg-demo --parameters skuName=something --parameters storageAccountName=289jijdj3ei

InvalidTemplate - Deployment template validation failed: 'The provided value for the template parameter 'skuName' is not valid. The value 'something' is not part of the allowed value(s): 'Standard_LRS,Standard_GRS,Standard_RAGRS,Standard_ZRS,Premium_LRS'.'.

Now the what-if analysis is catching the wrong value

Metadata

In addition to decorators, metadata can enhance the bicep file you are working on. Metadata doesn't have any particular keywords. It can be anything. The syntax is simply metadata <metadata-name> = ANY.

Metadata also doesn't enforce any validations on the bicep file. It is pure annotations of the content. You could add more context to the file to explain your decisions and give developers and users of the script a better understanding.

Taking the above example, let's annotate this file with additional metadata information.

metadata name = 'Simple Storage Module'
metadata summary = '''
This storage module is used for non critical business data.
'''

@minLength(3)
@maxLength(24)
@description('Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.')
param storageAccountName string

@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Standard_ZRS'
  'Premium_LRS'
])
@description('Please talk with the subscription owner if a different SKU is needed')
param skuName string = 'Standard_LRS'

param location string = resourceGroup().location

@description('''
Please specify here an object that includes the environment and the owner
of the corresponding resource. See the example
''')
@metadata({
    example: {
        env: 'dev'
        owner: 'bar'
    }
})
param tags object = {
        env: 'dev'
        owner: 'foo'
    }

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: skuName
  }
  kind: 'StorageV2'
  tags: tags
}

@description('Name of the storage account created. Can be used to reference the resource')
output name string = storageAccount.name

A more comprehensive use of metadata

Beneath the metadata fields' name and summary, there is also descriptions and a metadata decorator on top of the parameter tags.

Generate Markdown Documentation

We now have everything in the Bicep to generate the markdown documentation. We need an additional component to install on the system to do this. This component is PSDocs for Azure.

PSDocs for Azure - Overview
Generate documentation from Azure infrastructure as code (IaC) artifacts.

PSDocs for Azure

This tool takes an ARM template as input and uses it as a base for generating markdown documentation. It also leverages any metadata included in the ARM template.

The fun fact is that when working with Bicep, we don't have any ARM templates by default.

In order to use it we need a script that

  1. Converts the bicep modules into ARM templates
  2. Runs PSDocs.Azure to generate the documentation
  3. Combine multiple docs into one documentation file

As a prerequisite, we need PSDocs.Azure installed on our Powershell environment. The detailed instructions here.

Conversion Script

I created a Powershell script that will generate based on the folder where the biceps store the corresponding documentation and collect them into one final document stored in the desired output folder.

generate-bicep-documentation.ps1
GitHub Gist: instantly share code, notes, and snippets.

Powershell script to generate markdown documentation using PSDocs.Azure

To use the script on the current project root and store it in the folder docs with the name readme.md you call it

.\tools\generate-bicep-documentation.ps1 -BicepDirectory . -DestinationFile docs/readme.md

The Result

The storage module documentation in our sample is created using the generation script before and contains all metadata and decorator annotations, which look like this.

In addition to calling such scripts manually, you can create a pipeline step in your CI/CD system to generate and update that documentation on each push so you don't have outdated documents when people look things up.