Use existing virtual network with Azure Managed Applications

This article shows you how to define an Azure Managed Application that integrates with an existing virtual network in the consumer's subscription. The managed application lets the consumer decide whether to create a new virtual network or use an existing one. The existing virtual network can be outside of the managed resource group.

Main template

First, let's look at the mainTemplate.json file. The whole template for deploying a virtual machine and its associated resources is shown below. Later, you'll examine more closely the parts of the template that are related to using an existing virtual network.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "metadata": {
        "description": "Deployment location"
      }
    },
    "windowsOSVersion": {
      "type": "string",
      "defaultValue": "2016-Datacenter",
      "allowedValues": [
        "2008-R2-SP1",
        "2012-Datacenter",
        "2012-R2-Datacenter",
        "2016-Nano-Server",
        "2016-Datacenter-with-Containers",
        "2016-Datacenter",
        "2019-Datacenter"
      ],
      "metadata": {
        "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version."
      }
    },
    "vmName": {
      "type": "string",
      "metadata": {
        "title": "VM Name",
        "description": "This is the name of the your VM"
      }
    },
    "adminUsername": {
      "type": "string",
      "defaultValue": "testadmin",
      "metadata": {
        "description": "Username for the Virtual Machine."
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Password for the Virtual Machine."
      }
    },
    "virtualNetworkName": {
      "type": "string",
      "metadata": {
        "description": "New or Existing VNet Name"
      }
    },
    "virtualNetworkNewOrExisting": {
      "type": "string",
      "metadata": {
        "description": "Boolean indicating whether the VNet is new or existing"
      }
    },
    "virtualNetworkAddressPrefix": {
      "type": "string",
      "metadata": {
        "description": "VNet address prefix"
      }
    },
    "virtualNetworkResourceGroup": {
      "type": "string",
      "metadata": {
        "description": "Resource group of the VNet"
      }
    },
    "virtualMachineSize": {
      "type": "string",
      "metadata": {
        "description": "The size of the VM"
      }
    },
    "subnetName": {
      "type": "string",
      "metadata": {
        "description": "New or Existing subnet Name"
      }
    },
    "subnetAddressPrefix": {
      "type": "string",
      "metadata": {
        "description": "Subnet address prefix"
      }
    },
    "baseUrl": {
      "type": "string",
      "defaultValue": "",
      "metadata": {
        "artifactsBaseUrl": "",
        "description": "URL to acquire other templates"
      }
    }
  },
  "variables": {
    "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'sawinvm')]",
    "publicIPAddressName": "[concat(uniqueString(resourceGroup().id),'IP')]",
    "vmName": "[parameters('vmName')]",
    "nicName": "[concat(parameters('vmName'),'Nic')]",
    "vnetId": {
      "new": "[resourceId('Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]",
      "existing": "[resourceId(parameters('virtualNetworkResourceGroup'),'Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]"
    },
    "subnetId": "[concat(variables('vnetId')[parameters('virtualNetworkNewOrExisting')],'/subnets/',parameters('subnetName'))]",
    "publicIPAddressType": "Dynamic"
  },
  "resources": [
    {
      "condition": "[equals(parameters('virtualNetworkNewOrExisting'),'new')]",
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2021-02-01",
      "name": "[parameters('virtualNetworkName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[parameters('virtualNetworkAddressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[parameters('subnetName')]",
            "properties": {
              "addressPrefix": "[parameters('subnetAddressPrefix')]"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[variables('publicIPAddressName')]",
      "apiVersion": "2021-02-01",
      "location": "[parameters('location')]",
      "properties": {
        "publicIPAllocationMethod": "[variables('publicIPAddressType')]"
      }
    },
    {
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[variables('nicName')]",
      "apiVersion": "2021-02-01",
      "location": "[parameters('location')]",
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
              },
              "subnet": {
                "id": "[variables('subnetId')]"
              }
            }
          }
        ],
        "enableIPForwarding": true
      },
      "dependsOn": [
        "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]"
      ]
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage",
      "properties": {}
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2021-04-01",
      "name": "[variables('vmName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
        "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('virtualMachineSize')]"
        },
        "osProfile": {
          "computerName": "[variables('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "MicrosoftWindowsServer",
            "offer": "WindowsServer",
            "sku": "[parameters('windowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage"
          },
          "dataDisks": [
            {
              "diskSizeGB": 1023,
              "lun": 0,
              "createOption": "Empty"
            }
          ]
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
            "enabled": true,
            "storageUri": "[reference(resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))).primaryEndpoints.blob]"
          }
        }
      }
    }
  ]
}

Notice that the virtual network is conditionally deployed. The consumer passes in a parameter value that indicates whether to create a new or use existing virtual network. If the consumer selects a new virtual network, the resource is deployed. Otherwise, the resource is skipped during deployment.

{
  "condition": "[equals(parameters('virtualNetworkNewOrExisting'),'new')]",
  "type": "Microsoft.Network/virtualNetworks",
  "apiVersion": "2021-02-01",
  "name": "[parameters('virtualNetworkName')]",
  "location": "[parameters('location')]",
  "properties": {
    "addressSpace": {
      "addressPrefixes": [
        "[parameters('virtualNetworkAddressPrefix')]"
      ]
    },
    "subnets": [
      {
        "name": "[parameters('subnetName')]",
        "properties": {
          "addressPrefix": "[parameters('subnetAddressPrefix')]"
        }
      }
    ]
  }
},

The variable for the virtual network ID has two properties. One property returns the resource ID when a new virtual network is deployed. The other property returns the resource ID when an existing virtual network is used. The resource ID for the existing virtual network includes the name of the resource group that contains the virtual network.

The subnet ID is constructed from the value for the virtual network ID. It uses the value matches the consumers selection.

"variables": {
  "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'sawinvm')]",
  "publicIPAddressName": "[concat(uniqueString(resourceGroup().id),'IP')]",
  "vmName": "[parameters('vmName')]",
  "nicName": "[concat(parameters('vmName'),'Nic')]",
  "vnetId": {
    "new": "[resourceId('Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]",
    "existing": "[resourceId(parameters('virtualNetworkResourceGroup'),'Microsoft.Network/virtualNetworks',parameters('virtualNetworkName'))]"
  },
  "subnetId": "[concat(variables('vnetId')[parameters('virtualNetworkNewOrExisting')],'/subnets/',parameters('subnetName'))]",
  "publicIPAddressType": "Dynamic"
},

The network interface is set to the subnet ID variable.

{
  "type": "Microsoft.Network/networkInterfaces",
  "name": "[variables('nicName')]",
  "apiVersion": "2021-02-01",
  "location": "[parameters('location')]",
  "properties": {
    "ipConfigurations": [
      {
        "name": "ipconfig1",
        "properties": {
          "privateIPAllocationMethod": "Dynamic",
          "publicIPAddress": {
            "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
          },
          "subnet": {
            "id": "[variables('subnetId')]"
          }
        }
      }
    ],
    "enableIPForwarding": true
  },

UI definition

Now, let's look at the createUiDefinition.json file. The whole file is:

{
  "handler": "Microsoft.Azure.CreateUIDef",
  "version": "0.1.2-preview",
  "parameters": {
    "basics": [],
    "steps": [
      {
        "name": "deploymentDetails",
        "label": "Deployment Details",
        "subLabel": {
          "preValidation": "Required",
          "postValidation": "Done"
        },
        "bladeTitle": "Deployment Details",
        "elements": [
          {
            "name": "virtualMachine",
            "type": "Microsoft.Common.Section",
            "elements": [
              {
                "name": "vmName",
                "type": "Microsoft.Common.TextBox",
                "label": "VM Name",
                "toolTip": "Name of your virtual machine",
                "constraints": {
                  "required": true
                }
              },
              {
                "name": "vmPassword",
                "type": "Microsoft.Compute.CredentialsCombo",
                "label": {
                  "password": "Password",
                  "confirmPassword": "Confirm password"
                },
                "toolTip": {
                  "password": ""
                },
                "constraints": {
                  "required": true,
                  "customPasswordRegex": "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{12,}$",
                  "customValidationMessage": "The password must be alphanumeric, contain at least 12 characters, and have at least 1 letter and 1 number."
                },
                "options": {
                  "hideConfirmation": false
                },
                "osPlatform": "Windows",
                "visible": true
              }
            ],
            "visible": true
          },
          {
            "name": "vnet",
            "type": "Microsoft.Network.VirtualNetworkCombo",
            "label": {
              "virtualNetwork": "Virtual Network",
              "subnets": "Subnets"
            },
            "defaultValue": {
              "name": "vmx-vnet",
              "addressPrefixSize": "/16"
            },
            "constraints": {
              "minAddressPrefixSize": "/24"
            },
            "subnets": {
              "subnet1": {
                "label": "Subnet",
                "defaultValue": {
                  "name": "vmx-subnet",
                  "addressPrefixSize": "/24"
                },
                "constraints": {
                  "minAddressPrefixSize": "/29",
                  "minAddressCount": 8,
                  "requireContiguousAddresses": true
                }
              }
            }
          },
          {
            "name": "VMSize",
            "type": "Microsoft.Compute.SizeSelector",
            "label": "VM size",
            "toolTip": "The size of virtual machine for VM.",
            "recommendedSizes": [
              "Standard_D2_v2",
              "Standard_D2_v3"
            ],
            "constraints": {
              "allowedSizes": [
                "Standard_D2_v2",
                "Standard_D2_v3"
              ],
              "excludedSizes": []
            },
            "osPlatform": "Windows",
            "imageReference": {
              "publisher": "MicrosoftWindowsServer",
              "offer": "WindowsServer",
              "sku": "2012-R2-Datacenter"
            }
          }
        ]
      },
      {
        "name": "identityDetails",
        "label": "Managed Identity Details",
        "subLabel": {
          "preValidation": "Required",
          "postValidation": "Done"
        },
        "bladeTitle": "Managed Identity Details",
        "elements": [
          {
            "name": "identity",
            "type": "Microsoft.ManagedIdentity.IdentitySelector",
            "label": "Managed Identity Configuration",
            "toolTip": {
              "systemAssignedIdentity": "Enable system assigned identity to grant the resource access to other existing resources.",
              "userAssignedIdentity": "Add user assigned identities to grant the resource access to other existing resources."
            },
            "defaultValue": {
              "systemAssignedIdentity": "Off"
            },
            "options": {
              "hideSystemAssignedIdentity": false,
              "hideUserAssignedIdentity": false
            },
            "visible": true
          }
        ]
      }
    ],
    "outputs": {
      "location": "[location()]",
      "vmName": "[steps('deploymentDetails').virtualMachine.vmName]",
      "adminPassword": "[steps('deploymentDetails').virtualMachine.vmPassword.password]",
      "virtualNetworkName": "[steps('deploymentDetails').vnet.name]",
      "virtualNetworkNewOrExisting": "[steps('deploymentDetails').vnet.newOrExisting]",
      "virtualNetworkAddressPrefix": "[first(steps('deploymentDetails').vnet.addressPrefixes)]",
      "virtualNetworkResourceGroup": "[steps('deploymentDetails').vnet.resourceGroup]",
      "virtualMachineSize": "[steps('deploymentDetails').VMSize]",
      "subnetName": "[steps('deploymentDetails').vnet.subnets.subnet1.name]",
      "subnetAddressPrefix": "[steps('deploymentDetails').vnet.subnets.subnet1.addressPrefix]",
      "managedIdentity": "[steps('identityDetails').identity]"
    }
  }
}

The file includes a virtual network element.

{
  "name": "vnet",
  "type": "Microsoft.Network.VirtualNetworkCombo",
  "label": {
    "virtualNetwork": "Virtual Network",
    "subnets": "Subnets"
  },
  "defaultValue": {
    "name": "vmx-vnet",
    "addressPrefixSize": "/16"
  },
  "constraints": {
    "minAddressPrefixSize": "/24"
  },
  "subnets": {
    "subnet1": {
      "label": "Subnet",
      "defaultValue": {
        "name": "vmx-subnet",
        "addressPrefixSize": "/24"
      },
      "constraints": {
        "minAddressPrefixSize": "/29",
        "minAddressCount": 8,
        "requireContiguousAddresses": true
      }
    }
  }
},

That element lets the consumer select either a new or existing virtual network.

New or existing virtual network

In the outputs, you include a value that indicates whether the consumer selected a new or existing virtual network. There's also a managed identity value.

Note

The output value for the managed identity must be named managedIdentity.

"outputs": {
  "location": "[location()]",
  "vmName": "[steps('deploymentDetails').virtualMachine.vmName]",
  "adminPassword": "[steps('deploymentDetails').virtualMachine.vmPassword.password]",
  "virtualNetworkName": "[steps('deploymentDetails').vnet.name]",
  "virtualNetworkNewOrExisting": "[steps('deploymentDetails').vnet.newOrExisting]",
  "virtualNetworkAddressPrefix": "[first(steps('deploymentDetails').vnet.addressPrefixes)]",
  "virtualNetworkResourceGroup": "[steps('deploymentDetails').vnet.resourceGroup]",
  "virtualMachineSize": "[steps('deploymentDetails').VMSize]",
  "subnetName": "[steps('deploymentDetails').vnet.subnets.subnet1.name]",
  "subnetAddressPrefix": "[steps('deploymentDetails').vnet.subnets.subnet1.addressPrefix]",
  "managedIdentity": "[steps('identityDetails').identity]"
}

Next steps

To learn more about creating the UI definition file, see CreateUiDefinition.json for Azure managed application's create experience.