Convert ARM Template to Bicep and Deploy with Azure DevOps - Part 3 - Refactor
This is the third part of the series where an ARM Template is converted to Bicep. In this article, we should continue on Phase 3 - Refactor.
Phased Migration
As a reference to Part 2, Microsoft suggests a migration in 5 phases, to convert the ARM Template into Bicep Template.
The comprehensive details provided by Microsoft Docs are available as a reference1.
Phase 3 - Refactor
On this refactor, we should end up with a version that is streamlined, modular, ready to go for Testing and then for Production.
At the end of the previous migration, the bicep template created was in a good shape, as we had fixed and removed warnings.
On this refactor our goal is to perform:
- Review of resource API versions
Considering the bicep template resource API reference. e.g.: Microsoft.Web/sites@2020-12-01
Is that enough for you to build your template? Do you need to add a property that is not available on VStudio Code?
These are questions you should ask yourself to check whether the resource API you have referenced is enough or not. In this example, the resource API seems to do the job.
- Review of linter warnings and errors
As we are using the Bicep extension for Visual Studio, we have linting of suggestions, warnings and errors as the Bicep template is loaded. Make the appropriate changes to remove those annoying warnings.
- Review of parameters, variables and any symbolic names
When we decompiled the JSON onto bicep, parameters, variables and symbolic names were created automatically.
I like professional solutions by leveraging naming conventions. Best practices2 indicate the use of camelCase
.
- Review to simplify expressions
What sort of expressions do we need to simplify?
In general, this is aligned with the review of parameters and variables, because this will be created automatically during the decompilation.
In this example I use interpolation
.e.g.: var webSiteName = 'sample-web-${environment}'
rather than using functions like concat()
.
- Review of parent/child references
When creating resources on Azure, some components become parents
, and others, because of dependencies become children
. This is because of how resources are created, and the sequence they need to be provisioned.
In this refactor some of these references are used (available in the code below), consider a resource where you would have string concatenation for a resource like '${parent}/${child}'
, e.g.: ${webSiteName}/web
.
- Review of modular scripts
This is a great way of making templates from Bicep scripts, and enabling reuse of components, with the use of Bicep modules
. This will make a huge difference in how productive you/your team will become.
- Review of comments and descriptions
Check the existing comments and descriptions, and at the same time, feel free to complement templates with extra details/information.
- Review of best practices
Microsoft provides some best practices2 for parameters, variables, names and definitions, that are important to be double-checked.
Modules
After these considerations, let's break the bicep file azuredeploy.bicep
into modules.
Looking at the resources we have: App Plan, Web App and Application Insights. So let's split them in different bicep files. Use this folder/file structure:
azuredeploy.bicep
|--modules
|--|--appPlan.bicep
|--|--apps.bicep
|--|--insights.bicep
File appPlan.bicep
:
@minLength(1)
@maxLength(3)
param environment string
@minLength(3)
@maxLength(40)
param appPlanName string
@description('Describes plan\'s pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/?WT.mc_id=AZ-MVP-5005172')
@allowed([
'F1'
'D1'
'B1'
'B2'
'B3'
'S1'
'S2'
'S3'
'P1'
'P2'
'P3'
'P4'
])
param appPlanSku string = 'B1'
param location string = resourceGroup().location
@description('Resource: App Plan')
resource appPlan 'Microsoft.Web/serverfarms@2021-02-01' = {
name: appPlanName
location: location
sku: {
name: appPlanSku
}
kind: 'app'
tags: {
displayName: 'HostingPlan'
environment: environment
}
}
output appPlanId string = appPlan.id
File insights.bicep
:
@minLength(1)
@maxLength(3)
param environment string
@minLength(3)
@maxLength(60)
param webSiteName string
param location string = resourceGroup().location
@description('Resource: Application Insights')
resource aiWeb 'Microsoft.Insights/components@2020-02-02' = {
name: webSiteName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
tags: {
displayName: 'AppInsightsComponent'
environment: environment
}
}
output aiWebInstrumentationKey string = aiWeb.properties.InstrumentationKey
File apps.bicep
:
@minLength(1)
@maxLength(3)
param environment string
@minLength(3)
@maxLength(60)
param webSiteName string
param appPlanId string
param webAppInsights string
param location string = resourceGroup().location
var aspnetEnvironment = {
dev: 'Development'
tst: 'Test'
uat: 'UAT'
prd: 'Production'
}
@description('Resource: Apps')
resource appWeb 'Microsoft.Web/sites@2020-12-01' = {
name: webSiteName
location: location
kind: 'app'
properties:{
serverFarmId: appPlanId
}
tags: {
displayName: 'Website'
environment: environment
}
}
@description('Resource: App Config')
resource appWebConfig 'Microsoft.Web/sites/config@2020-12-01' = {
name: '${webSiteName}/web'
dependsOn: [
appWeb
]
properties: {
appSettings: [
{
name: 'ASPNETCORE_ENVIRONMENT'
value: aspnetEnvironment[environment]
}
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: webAppInsights
}
]
}
}
File azuredeploy.bicep
:
@minLength(1)
@maxLength(3)
param environment string
@minLength(3)
param appPlanName string
@description('Describes plan\'s pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/?WT.mc_id=AZ-MVP-5005172')
@allowed([
'F1'
'D1'
'B1'
'B2'
'B3'
'S1'
'S2'
'S3'
'P1'
'P2'
'P3'
'P4'
])
param appPlanSku string = 'F1'
param location string = resourceGroup().location
var resourceGroups = {
dev: 'sample-rg-nonprd'
tst: 'sample-rg-nonprd'
uat: 'sample-rg-uat'
prd: 'sample-rg-prd'
}
var webSiteName = 'sample-web-${environment}'
var resourceGroupName = resourceGroups[environment]
@description('Module: App Plan')
module appPlan './modules/appPlan.bicep' = {
name: 'appPlanModule'
params: {
appPlanName: appPlanName
appPlanSku: appPlanSku
location: location
environment: environment
}
scope: resourceGroup(resourceGroupName)
}
@description('Module: Application Insights')
module ai './modules/insights.bicep' = {
name: 'appInsightsModule'
params: {
webSiteName: webSiteName
location: location
environment: environment
}
}
@description('Module: Apps')
module app './modules/apps.bicep' = {
name: 'appModule'
params: {
appPlanId: appPlan.outputs.appPlanId
webSiteName: webSiteName
webAppInsights: ai.outputs.aiWebInstrumentationKey
location: location
environment: environment
}
}
The files were split. Note that the dependency between webApp and app Insights were modified, and some ASPNET environment variables were added to the apps.bicep
.
Observe the use of output variables as well, introduced because the modules split into different files.
Because of the different resource groups we are going to use, the scope was introduced on the appPlan.
Compiling
To make sure that we can compile the bicep changes, run the following command:
bicep build azuredeploy.bicep
No suggestions, errors or warnings are expected. After compilation, the file azuredeploy.json
should be generated, and with that, we have achieved our goal of reviewing the different parts of this Phase 3, making this template modular and compliant with Bicep best practices.
In Phase 4 we'll adjust the variables for Testing and get to Test the new Bicep template.
Full Series
Phase 3 - Refactor
- References: Migrate to Bicep↩
- Best practices: Best practices↩