Exercise - Refactor the Bicep file
In the previous exercise, you created an initial Bicep file that contains the toy truck virtual machine and associated resources. However, the Bicep template doesn't follow best practices, and it's a little hard to read. In this unit, you'll refactor the file.
During the refactoring process, you'll:
- Update the symbolic names for your resources and parameters.
- Remove redundant parameters, resources, and properties.
- Add variables and parameters to make your Bicep file reusable.
Update the resource symbolic names
In Visual Studio Code, open the main.bicep file.
Select the symbolic name for the network security group resource, which is
networkSecurityGroups_ToyTruckServer_nsg_name_resource
or a similar name.Rename the symbolic name. You can select F2 or right-click and then select Rename Symbol.
Enter the name
networkSecurityGroup
and press Enter. Visual Studio Code updates the name and all references in the file.Repeat this process for each resource. Rename the resources as shown in the following table.
Note
The names of the resources in your deployment will be a little different from the names in the table. Find the resources that have names that are close to these names.
Resource type Current symbolic name New symbolic name Public IP address publicIPAddresses_ToyTruckServer_ip_name_resource
publicIPAddress
Virtual machine virtualMachines_ToyTruckServer_name_resource
virtualMachine
Virtual network virtualNetworks_ToyTruck_vnet_name_resource
virtualNetwork
Subnet virtualNetworks_ToyTruck_vnet_name_default
defaultSubnet
Network interface networkInterfaces_toytruckserver890_name_resource
networkInterface
Remove the redundant subnet resource
The virtual network's subnet is currently defined twice. It's defined once in the virtualNetwork
resource and again as its own child resource named defaultSubnet
. It doesn't make sense to define the subnet twice.
Delete the
defaultSubnet
resource.Notice that the
networkInterface
resource now displays a problem, because it refers to the default subnet's resource ID:Update the
virtualNetwork
resource to include anexisting
reference to the subnet. If you add theexisting
reference, you can refer to the subnet again in your Bicep code without defining it again:resource virtualNetwork 'Microsoft.Network/virtualNetworks@2020-11-01' = { name: virtualNetworks_ToyTruck_vnet_name location: 'westus' properties: { addressSpace: { addressPrefixes: [ '10.0.0.0/16' ] } subnets: [ { name: 'default' properties: { addressPrefix: '10.0.0.0/24' delegations: [] privateEndpointNetworkPolicies: 'Enabled' privateLinkServiceNetworkPolicies: 'Enabled' } } ] virtualNetworkPeerings: [] enableDdosProtection: false } resource defaultSubnet 'subnets' existing = { name: 'default' } }
Update the
networkInterface
resource to refer to the subnet's resource ID:resource networkInterface 'Microsoft.Network/networkInterfaces@2022-05-01' = { name: networkInterfaces_toytruckserver890_name location: 'westus3' properties: { ipConfigurations: [ { name: 'ipconfig1' properties: { privateIPAddress: '10.0.0.4' privateIPAllocationMethod: 'Dynamic' publicIPAddress: { id: publicIPAddress.id } subnet: { id: virtualNetwork::defaultSubnet.id } primary: true privateIPAddressVersion: 'IPv4' } } ] dnsSettings: { dnsServers: [] } enableAcceleratedNetworking: true enableIPForwarding: false disableTcpStateTracking: false networkSecurityGroup: { id: networkSecurityGroup.id } nicType: 'Standard' } }
You will notice an error about that the expression is involved in a cycle. You'll fix that in the next step.
Go to the
virtualNetwork
resource'ssubnets
property and removeid: defaultSubnet.id
to resolve the error.
Change the parameters to variables
The parameters in the template don't need to be parameters. You'll now rename the parameters to more meaningful names and convert them to variables.
Select the symbolic name for the
virtualNetworks_ToyTruck_vnet_name
parameter. Rename it tovirtualNetworkName
.Change the parameter to a variable. Remember to remove the type because variable definitions don't include types:
var virtualNetworkName = 'ToyTruck-vnet'
Repeat the process for each parameter. Rename the parameters as shown in the following table.
Notice that the value of the
networkInterfaceName
includes a three-digit number. The number is different for different deployments. Ensure that you copy the variable's value from your reference template.Current parameter name New variable name virtualMachines_ToyTruckServer_name
virtualMachineName
networkInterfaces_toytruckserver890_name
networkInterfaceName
publicIPAddresses_ToyTruckServer_ip_name
publicIPAddressName
networkSecurityGroups_ToyTruckServer_nsg_name
networkSecurityGroupName
Verify that your variable declarations look like the following example:
var virtualNetworkName = 'ToyTruck-vnet' var virtualMachineName = 'ToyTruckServer' var networkInterfaceName = 'YOUR-NETWORK-INTERFACE-NAME' var publicIPAddressName = 'ToyTruckServer-ip' var networkSecurityGroupName = 'ToyTruckServer-nsg'
Update the resource locations
All the resources currently use a hard-coded location. You'll now add a parameter so that the template becomes more reusable.
At the top of the file, add a new parameter and a description decorator to clarify the parameter's purpose:
@description('The location where resources are deployed.') param location string = resourceGroup().location
Update each resource to use the
location
parameter instead of the hard-codedwestus3
location.
Add parameters and variables
Your template has some hard-coded values where parameters or variables would be more appropriate. You'll now add parameters for properties that might change between deployments and variables for values that won't.
At the top of the main.bicep file, below the
location
parameter, add the following parameters:@description('The name of the size of the virtual machine to deploy.') param virtualMachineSizeName string = 'Standard_D2s_v3' @description('The name of the storage account SKU to use for the virtual machine\'s managed disk.') param virtualMachineManagedDiskStorageAccountType string = 'Premium_LRS' @description('The administrator username for the virtual machine.') param virtualMachineAdminUsername string = 'toytruckadmin' @description('The administrator password for the virtual machine.') @secure() param virtualMachineAdminPassword string @description('The name of the SKU of the public IP address to deploy.') param publicIPAddressSkuName string = 'Standard' @description('The virtual network address range.') param virtualNetworkAddressPrefix string @description('The default subnet address range within the virtual network') param virtualNetworkDefaultSubnetAddressPrefix string
Some of the parameters have default values and others don't. Later, you'll create a parameter file to set most of these values.
Add the following new variable declarations below the
networkSecurityGroupName
variable:var virtualNetworkDefaultSubnetName = 'default' var virtualMachineImageReference = { publisher: 'canonical' offer: '0001-com-ubuntu-server-focal' sku: '20_04-lts-gen2' version: 'latest' }
Add the following variable declaration. Replace the value with the OS disk name from your own reference template.
var virtualMachineOSDiskName = 'YOUR-OS-DISK-NAME'
The value of the
virtualMachineOSDiskName
is unique. The value is different for each deployment. Ensure that you copy the variable's value from your reference template.Warning
Ensure that you copy the correct values for the
virtualMachineOSDiskName
andnetworkInterfaceName
variables. Otherwise, Azure won't detect that you're declaring existing resources and might try to create new resources.Your variable declarations should now look like this example:
var virtualNetworkName = 'ToyTruck-vnet' var virtualMachineName = 'ToyTruckServer' var networkInterfaceName = 'YOUR-NETWORK-INTERFACE-NAME' var publicIPAddressName = 'ToyTruckServer-ip' var networkSecurityGroupName = 'ToyTruckServer-nsg' var virtualNetworkDefaultSubnetName = 'default' var virtualMachineImageReference = { publisher: 'canonical' offer: '0001-com-ubuntu-server-focal' sku: '20_04-lts-gen2' version: 'latest' } var virtualMachineOSDiskName = 'YOUR-OS-DISK-NAME'
Update the
publicIPAddress
resource to refer to a parameter:Property Parameter sku.name
publicIPAddressSkuName
Update the
virtualMachine
resource to refer to the parameters and variables:Property Parameter or variable hardwareProfile.vmSize
virtualMachineSizeName
storageProfile.imageReference
virtualMachineImageReference
Use the variable name to replace the object's values, including the curly braces.storageProfile.osDisk.name
virtualMachineOSDiskName
storageProfile.osDisk.managedDisk.storageAccountType
virtualMachineManagedDiskStorageAccountType
osProfile.adminUsername
virtualMachineAdminUsername
osProfile.adminPassword
Add this property belowosProfile.adminUsername
virtualMachineAdminPassword
Update the
virtualNetwork
resource to refer to the parameters and variables:Property Parameter or variable addressSpace.addressPrefixes
virtualNetworkAddressPrefix
subnets.name
virtualNetworkDefaultSubnetName
subnets.addressPrefix
virtualNetworkDefaultSubnetAddressPrefix
Update the
virtualNetwork
resource's nested resourcedefaultSubnet
:Property Variable name
virtualNetworkDefaultSubnetName
Remove unnecessary properties
The export process adds redundant properties to many resources. Use these steps to remove the unneeded properties.
In the
networkSecurityGroup
resource, removeproperties
because thesecurityRules
property is empty.In the
publicIPAddress
resource, remove the following properties:ipAddress
property because it's automatically set by AzureipTags
property because it's empty
In the
virtualMachine
resource, remove the following properties:storageProfile.osDisk.managedDisk.id
property because Azure automatically determines this property when the virtual machine is deployedImportant
If you don't remove this property, your template won't deploy correctly.
storageProfile.dataDisks
property because it's emptyosProfile.secrets
property because it's emptyosProfile.requireGuestProvisionSignal
property because Azure sets this property automatically
In the
virtualNetwork
resource, remove the following properties:delegations
andvirtualNetworkPeerings
properties because they're empty.- The line for
type: 'Microsoft.Network/virtualNetworks/subnets'
In the
networkInterface
resource, remove the following properties:The
kind
propertyFrom
ipConfigurations
:id
,etag
,type
, andprivateIPAddress
because it's automatically set by Azure and the allocation method is DynamicFrom
ipConfigurations.properties
:provisioningState
From
publicIPAddress
,name
,properties
,type
, andsku
dnsSettings
because thednsServers
property is empty
Tip
When you work with your own templates, you need to determine whether there are any properties that should be removed as you did here.
In Visual Studio Code, the Bicep extension helps you set the minimum properties for a resource. When you add a space after the equal sign in the resource definition, Visual Studio Code prompts you to select required-properties:
When you select required-properties, Visual Studio Code populates the resource definition with the properties that are mandatory. You can refer to required-properties to determine whether the properties in your converted template all need to be present.
The Azure Quickstart Templates repository is also helpful for this task. Find a quickstart template that does approximately what you're trying to do, and look at the properties it sets on the resource.
Create a parameter file
Your parameters are currently defined as default values in your template. To make your template work well across environments, it's a good idea to create a parameter file, and to remove default values for parameters that need to change for each environment.
Create a new file named main.parameters.production.json.
Paste the following JSON into the main.parameters.production.json file:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "virtualMachineSizeName": { "value": "Standard_D2s_v3" }, "virtualMachineManagedDiskStorageAccountType": { "value": "Premium_LRS" }, "virtualMachineAdminUsername": { "value": "toytruckadmin" }, "virtualNetworkAddressPrefix": { "value": "YOUR-VIRTUAL-NETWORK-ADDRESS-PREFIX" }, "virtualNetworkDefaultSubnetAddressPrefix": { "value": "YOUR-SUBNET-ADDRESS-PREFIX" } } }
Update the values for the
virtualNetworkAddressPrefix
andvirtualNetworkDefaultSubnetAddressPrefix
parameters to match the IP address ranges that are specified in your reference template's virtual network resource.For example, here's how the values are specified in a reference template. Your IP addresses might be different from the IP addresses that are used in this example.
resource virtualNetworks_ToyTruck_vnet_name_resource 'Microsoft.Network/virtualNetworks@2022-05-01' = { name: virtualNetworks_ToyTruck_vnet_name location: 'westus3' properties: { addressSpace: { addressPrefixes: [ '10.0.0.0/16' ] } subnets: [ { name: 'default' id: virtualNetworks_ToyTruck_vnet_name_default.id properties: { addressPrefix: '10.0.0.0/24' delegations: [] privateEndpointNetworkPolicies: 'Disabled' privateLinkServiceNetworkPolicies: 'Enabled' } type: 'Microsoft.Network/virtualNetworks/subnets' } ] virtualNetworkPeerings: [] enableDdosProtection: false } }
Update your main.bicep file to remove the default values for the parameters you specified in the parameters file.
virtualMachineSizeName
virtualMachineManagedDiskStorageAccountType
virtualMachineAdminUsername
Don't change the default values for the location
and publicIPAddressSkuName
parameters because those values are probably the same for all your environments.
Verify your template
At the end of the refactor phase, your main.bicep file should look similar to the following example:
@description('The location where resources are deployed.') param location string = resourceGroup().location @description('The name of the size of the virtual machine to deploy.') param virtualMachineSizeName string @description('The name of the storage account SKU to use for the virtual machine\'s managed disk.') param virtualMachineManagedDiskStorageAccountType string @description('The administrator username for the virtual machine.') param virtualMachineAdminUsername string @description('The administrator password for the virtual machine.') @secure() param virtualMachineAdminPassword string @description('The name of the SKU of the public IP address to deploy.') param publicIPAddressSkuName string = 'Standard' @description('The virtual network address range.') param virtualNetworkAddressPrefix string @description('The default subnet address range within the virtual network') param virtualNetworkDefaultSubnetAddressPrefix string var virtualNetworkName = 'ToyTruck-vnet' var virtualMachineName = 'ToyTruckServer' var networkInterfaceName = 'YOUR-NETWORK-INTERFACE-NAME' var publicIPAddressName = 'ToyTruckServer-ip' var networkSecurityGroupName = 'ToyTruckServer-nsg' var virtualNetworkDefaultSubnetName = 'default' var virtualMachineImageReference = { publisher: 'canonical' offer: '0001-com-ubuntu-server-focal' sku: '20_04-lts-gen2' version: 'latest' } var virtualMachineOSDiskName = 'YOUR-OS-DISK-NAME' resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-05-01' = { name: networkSecurityGroupName location: location } resource publicIPAddress 'Microsoft.Network/publicIPAddresses@2022-05-01' = { name: publicIPAddressName location: location sku: { name: publicIPAddressSkuName tier: 'Regional' } properties: { publicIPAddressVersion: 'IPv4' publicIPAllocationMethod: 'Static' idleTimeoutInMinutes: 4 } } resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-08-01' = { name: virtualMachineName location: location properties: { hardwareProfile: { vmSize: virtualMachineSizeName } storageProfile: { imageReference: virtualMachineImageReference osDisk: { osType: 'Linux' name: virtualMachineOSDiskName createOption: 'FromImage' caching: 'ReadWrite' managedDisk: { storageAccountType: virtualMachineManagedDiskStorageAccountType } deleteOption: 'Delete' diskSizeGB: 30 } } osProfile: { computerName: virtualMachineName adminUsername: virtualMachineAdminUsername adminPassword: virtualMachineAdminPassword linuxConfiguration: { disablePasswordAuthentication: false provisionVMAgent: true patchSettings: { patchMode: 'ImageDefault' assessmentMode: 'ImageDefault' } enableVMAgentPlatformUpdates: false } allowExtensionOperations: true } networkProfile: { networkInterfaces: [ { id: networkInterface.id properties: { deleteOption: 'Detach' } } ] } diagnosticsProfile: { bootDiagnostics: { enabled: true } } } } resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-05-01' = { name: virtualNetworkName location: location properties: { addressSpace: { addressPrefixes: [ virtualNetworkAddressPrefix ] } subnets: [ { name: virtualNetworkDefaultSubnetName properties: { addressPrefix: virtualNetworkDefaultSubnetAddressPrefix privateEndpointNetworkPolicies: 'Disabled' privateLinkServiceNetworkPolicies: 'Enabled' } } ] enableDdosProtection: false } resource defaultSubnet 'subnets' existing = { name: virtualNetworkDefaultSubnetName } } resource networkInterface 'Microsoft.Network/networkInterfaces@2022-05-01' = { name: networkInterfaceName location: location properties: { ipConfigurations: [ { name: 'ipconfig1' properties: { privateIPAllocationMethod: 'Dynamic' publicIPAddress: { id: publicIPAddress.id } subnet: { id: virtualNetwork::defaultSubnet.id } primary: true privateIPAddressVersion: 'IPv4' } } ] enableAcceleratedNetworking: true enableIPForwarding: false disableTcpStateTracking: false networkSecurityGroup: { id: networkSecurityGroup.id } nicType: 'Standard' } }
Your main.parameters.production.json file should look similar to the following file, although you might have different IP address ranges listed:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "virtualMachineSizeName": { "value": "Standard_D2s_v3" }, "virtualMachineManagedDiskStorageAccountType": { "value": "Premium_LRS" }, "virtualMachineAdminUsername": { "value": "toytruckadmin" }, "virtualNetworkAddressPrefix": { "value": "10.0.0.0/16" }, "virtualNetworkDefaultSubnetAddressPrefix": { "value": "10.0.0.0/24" } } }
Select View > Problems to display the problems pane.
No problems are indicated.
Tip
When you work with your own templates, you might make different choices about the properties to parameterize and other customizations. Throughout this module, we provide general guidance to help you get started, but you'll need to consider your own environment and how you want to reuse your templates when you decide how to refactor your own Bicep files.