Exercise - Refactor the Bicep file

Completed

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

  1. In Visual Studio Code, open the main.bicep file.

  2. 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.

  3. 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.

  1. Delete the defaultSubnet resource.

    Notice that the networkInterface resource now displays a problem, because it refers to the default subnet's resource ID:

    Screenshot of Visual Studio Code that shows the network interface resource definition. The error is highlighted.

  2. Update the virtualNetwork resource to include an existing reference to the subnet. If you add the existing 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'
      }
    }
    
  3. 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.

  4. Go to the virtualNetwork resource's subnets property and remove id: 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.

  1. Select the symbolic name for the virtualNetworks_ToyTruck_vnet_name parameter. Rename it to virtualNetworkName.

  2. Change the parameter to a variable. Remember to remove the type because variable definitions don't include types:

    var virtualNetworkName = 'ToyTruck-vnet'
    
  3. 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
  4. 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.

  1. 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
    
  2. Update each resource to use the location parameter instead of the hard-coded westus3 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.

  1. 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.

  2. 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'
    }
    
  3. 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 and networkInterfaceName 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'
    
  4. Update the publicIPAddress resource to refer to a parameter:

    Property Parameter
    sku.name publicIPAddressSkuName
  5. 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 below osProfile.adminUsername
    virtualMachineAdminPassword
  6. Update the virtualNetwork resource to refer to the parameters and variables:

    Property Parameter or variable
    addressSpace.addressPrefixes virtualNetworkAddressPrefix
    subnets.name virtualNetworkDefaultSubnetName
    subnets.addressPrefix virtualNetworkDefaultSubnetAddressPrefix
  7. Update the virtualNetwork resource's nested resource defaultSubnet:

    Property Variable
    name virtualNetworkDefaultSubnetName

Remove unnecessary properties

The export process adds redundant properties to many resources. Use these steps to remove the unneeded properties.

  1. In the networkSecurityGroup resource, remove properties because the securityRules property is empty.

  2. In the publicIPAddress resource, remove the following properties:

    • ipAddress property because it's automatically set by Azure
    • ipTags property because it's empty
  3. In the virtualMachine resource, remove the following properties:

    • storageProfile.osDisk.managedDisk.id property because Azure automatically determines this property when the virtual machine is deployed

      Important

      If you don't remove this property, your template won't deploy correctly.

    • storageProfile.dataDisks property because it's empty

    • osProfile.secrets property because it's empty

    • osProfile.requireGuestProvisionSignal property because Azure sets this property automatically

  4. In the virtualNetwork resource, remove the following properties:

    • delegations and virtualNetworkPeerings properties because they're empty.
    • The line for type: 'Microsoft.Network/virtualNetworks/subnets'
  5. In the networkInterface resource, remove the following properties:

    • The kind property

    • From ipConfigurations: id, etag, type, andprivateIPAddress because it's automatically set by Azure and the allocation method is Dynamic

    • From ipConfigurations.properties:

      • provisioningState
    • From publicIPAddress, name, properties, type, and sku

    • dnsSettings because the dnsServers 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:

Screenshot of Visual Studio Code that shows the required-properties option.

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.

  1. Create a new file named main.parameters.production.json.

  2. 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"
        }
      }
    }
    
  3. Update the values for the virtualNetworkAddressPrefix and virtualNetworkDefaultSubnetAddressPrefix 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
      }
    }
    
  4. 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

  1. 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"
        }
      }
    }
    
  2. 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.