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.
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.
Using Azure Bicep Modules offers several benefits:
In this article, we will be creating a resource group and storage account module. It will be performed in the following sequence:
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.
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
@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
Step 2: Populate the Resource Group Bicep file
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
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.
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
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.
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.
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.
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'
}
}
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 deployment sub create --name Deploypol --location eastus --template-file main.bicep --what-if
New-AzSubscriptionDeployment -name Demosubdeploy -Location eastus -TemplateFile "main.bicep" -WhatIf
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.