Posted: Saturday, May 20, 2023

Word Count: 1170

Reading Time: 6 minutes


In the world of Infrastructure as Code (IaC), managing large-scale environments can be a daunting task. Azure Bicep is a domain-specific language (DSL) that simplifies the creation of infrastructure for Azure resources. Bicep Modules take this a step further by providing a way to encapsulate and reuse infrastructure code in a modular and organized way. In this blog post, we will explore what Azure Bicep Modules are and how to create them.

What are Azure Bicep Modules?

Azure Bicep Modules are self-contained blocks of code that encapsulate a specific piece of infrastructure. A module can contain one or more Azure resources, along with any dependencies they may have. Modules can be used to create reusable building blocks for infrastructure code, which can then be shared and used across multiple projects.

Modules in Bicep are similar to modules in other programming languages. They provide a way to abstract away implementation details and expose a clean and simple interface for other modules or projects to consume.

Benefits of Using Azure Bicep Modules

Using Azure Bicep Modules offers several benefits:

  1. Reusability: Modules can be shared across multiple projects, reducing the amount of code duplication and making it easier to maintain consistency across environments.
  2. Modularity: Modules allow you to break down complex infrastructure into smaller, more manageable pieces. This makes it easier to understand and update your infrastructure as your needs evolve.
  3. Consistency: By encapsulating infrastructure code into modules, you can ensure that your infrastructure is consistent across different environments.
  4. Abstraction: Modules provide a level of abstraction that allows you to hide implementation details and expose a clean and simple interface to other modules or projects.

Creating an Azure Bicep Module

In this article, we will be creating a resource group and storage account module. It will be performed in the following sequence:

  1. Populate the Storage Account Bicep file
  2. Populate the Resource Group Bicep file
  3. Create main bicep file

Tools

A basic notepad is really all you need to create bicep; however, a source code editor such as visual studio code makes life easier. You can find the download link for visual studio code here.

For this exercise, I’ve created the following folder and file structure:

To follow along, create a similar structure, including the files. Leave them blank for the time being.

The Modules

When creating a module file, it’s important to define the output parameters. By default, Output parameters can be defined within the module and allows additional information to be passed on to other modules or to other parts of your infrastructure code. Common output parameters are typically the resource ID and connection strings. Let’s start by creating the modules.

Step 1: Populate the Storage Account Bicep file

  1. Open the storage account bicep file and copy the following code. Feel free to change the parameters’ values if you like.
storageAccount.bicep
@description('Storage Account type')
@allowed([
  'Premium_LRS'
  'Premium_ZRS'
  'Standard_GRS'
  'Standard_GZRS'
  'Standard_LRS'
  'Standard_RAGRS'
  'Standard_RAGZRS'
  'Standard_ZRS'
])
param storageAccountType string = 'Standard_LRS'

@description('The storage account location.')
param location string = resourceGroup().location

@maxLength(10)
@description('Prefix for all storage accounts')
param storageAccountPrefix string

@description('The name of the storage account')
param storageAccountName string = '${storageAccountPrefix}${uniqueString(resourceGroup().id)}'

resource sa 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: storageAccountName
  identity: {
    type: 'SystemAssigned'
  }
  location: location
  sku: {
    name: storageAccountType
  }
  kind: 'StorageV2'
  properties: {}
}

output storageAccountName string = storageAccountName
output storageAccountId string = sa.id
output storage string = sa.identity.principalId

  1. Save the file
  2. Done

Step 2: Populate the Resource Group Bicep file

  1. Open the resourceGroup.bicep file and copy the following code. Again, feel free to change the parameters’ values.
resourceGroup.bicep
targetScope = 'subscription'
param resourceGroupName string
param location string

resource rgroups 'Microsoft.Resources/resourceGroups@2022-09-01' = {
  name: resourceGroupName
  location: location
}

output location string = rgroups.location
output id string = rgroups.id
  1. Save the file
  2. Done

Next, define the parameters for your module. Parameters are variables that can be passed into the module at deployment time. They allow you to customize the behavior of your module without having to modify its code.

Main.bicep

The main.bicep file can be leveraged to call the modules and override any parameters set module. As needed, the modules can be called repeatedly to create multiple resources. Let’s go ahead and populate the main.bicep file.

Step 3: Populate main.bicep

  1. Open the main.bicep file and copy the following code. Again, feel free to change the parameters’ values.
main.bicep
targetScope = 'subscription'
param location string = 'eastus'


module marketingStorageResourceGroup './Modules/resourceGroup.bicep' = {
  name: 'Storage-Services-Marketing'
  params: {
    location: location
    resourceGroupName: 'Storage-Services-Marketing'
  }
}

module storageAccountMarketing 'Modules/storageAccount.bicep' = {
  scope: resourceGroup(marketingStorageResourceGroup.name)
  name: 'Marketing-Storage-Account'
  params: {
    storageAccountPrefix: 'market'
    location: location
  }
}

If main.bicep were to be executed, it would create a resourcegroup called “Storage-Services-Marketing” and a storage account with a prefix of market.

  1. Save the file
  2. Done

Module Naming

Modules typically have three required parameters:

The module name unique within the deployment. In many instances, the module and resource name is the same; however, it is not a requirements. If you review lines 6 and 9 in the main.bicep file, the name of the module and the resource group name is the same. However, the storage account leverages ‘Legal-Storage-Account’ as the module name, but only passes the prefix of market to the storage account module.

Define the outputs for your module. Outputs are values that the module will return after it has been deployed. They allow you to pass information from the module to other parts of your infrastructure code.

Expanding main.bicep

One of the major benefits of modules is the modularity it adds to the code set. If, for example, we wanted to create additional storage accounts and resource groups, it’s a simple matter of calling the module and changing the parameters accordingly. See the example below.

main.bicep (expanded)
targetScope = 'subscription'
param location string = 'eastus'


module marketingStorageResourceGroup './Modules/resourceGroup.bicep' = {
  name: 'Storage-Services-Marketing'
  params: {
    location: location
    resourceGroupName: 'Storage-Services-Marketing'
  }
}

module legalStorageResourceGroup './Modules/resourceGroup.bicep' = {
  name: 'Storage-Services-Legal'
  params: {
    location: location
    resourceGroupName: 'Storage-Services-Legal'
  }
}

module storageAccountMarketing 'Modules/storageAccount.bicep' = {
  scope: resourceGroup(marketingStorageResourceGroup.name)
  name: 'Marketing-Storage-Account'
  params: {
    storageAccountPrefix: 'market'
    location: location
  }
}

module storageAccountLegal 'Modules/storageAccount.bicep' = {
  scope: resourceGroup(legalStorageResourceGroup.name)
  name: 'Legal-Storage-Account'
  params: {
    storageAccountPrefix: 'legal'
    location: location
    storageAccountType: 'Standard_ZRS'
  }
}

Deployment Strategies

Three common ways to deploy this template are to leverage AZ CLI or PowerShell. Using the what-if switch allows you to execute the script and determine what will be created, modified or removed.

AZ CLI Example
AZ CLI
az deployment sub create --name Deploypol --location eastus --template-file main.bicep --what-if
PowerShell Example
PowerShell
New-AzSubscriptionDeployment -name Demosubdeploy -Location eastus -TemplateFile "main.bicep" -WhatIf                        

Conclusion

Azure Bicep Modules provide a powerful way to create reusable infrastructure code that is easy to understand and maintain. By encapsulating infrastructure code into modules, you can reduce code duplication, increase modularity, ensure consistency, and abstract away implementation details. In this blog post, we have seen how to create an Azure Bicep Module, including defining parameters and outputs, and using the module in a main Bicep file. By using Azure Bicep Modules, you can streamline your infrastructure code and make it easier to manage and scale.