Understanding Idempotence and Scope in Azure Resource Manager Templates

Azure resources can be provisioned and modified a multitude of ways, through the portal, via PowerShell or through templates. In this post we're going to take a look at the latter method, and specifically how the concept of idempotence and scope plays a role in template deployments. If you've never heard the term before, the term idempotent is defined by Merriam-Webster as "relating to or being a mathematical quantity which when applied to itself under a given binary operation (such as multiplication) equals itself". What this means in technology is the ability to apply one or more operations against a resource resulting in the same outcome. For instance, if I run a script on a system it should have the same outcome regardless of the number of times I execute the script rather than erring out or performing duplicate actions. Templates in Azure leverage the concept of idempotence, but it's important to understand how objects within a template operate in an idempotent manner and where the scope of the change comes in to consideration.

Template deployment scenarios

Let's get started by deploying some resources through a template. For these activities we're going to be using the 101-vm-simple-windows template in the Azure Quickstart repository on GitHub. This template deploys the following resources:

  • Storage account (for diagnostics storage)
  • Public IP address
  • Virtual network
  • Network interface
  • Virtual machine

Before we get started, let's do a little prep work. First, go here to view the raw text in the template. Save this file as 101-vm-simple-windows.json in a local directory. Keep the full path to this file handy, we'll need it agin here shortly.

Next, let's log in to Azure PowerShell and create a couple resource groups.

 Login-AzureRmAccount
$rg1 = (New-AzureRmResourceGroup -Name templateTest1 -Location WestUS).ResourceGroupName
$rg2 = (New-AzureRmResourceGroup -Name templateTest2 -Location WestUS).ResourceGroupName

Now that the resource groups are ready let's deploy the template by running the following commands. A few things to note here. You'll need to replace the path to the template file below with your local path. Change the password to something else as necessary. We also generate a random number to append to the DNS label to ensure that it's unique.

 $templateFile = "C:\templates\101-vm-simple-windows.json"
$adminUserName = "vmadmin"
$password = ConvertTo-SecureString "Password1234" -AsPlainText -Force
$dnsLabelPrefix = "myvm-$(Get-Random)"
$windowsOsVersion = "2016-Datacenter"
New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg1 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix `
    -windowsOSVersion $windowsOsVersion

Once this is done deploying feel free to hop into the portal to check it out if you like. You should see a pretty basic VM and all the other resources provisioned that we mentioned earlier. Your PowerShell window should show output pretty similar to the following.

Mindblowing, right? Now let's redeploy the template see what happens.

 New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg1 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix `
    -windowsOSVersion $windowsOsVersion

Once complete, you should see output similar to the below, and it likely took substantially less time.

If you take a look in the portal at your resources you should see that they were completely unchanged. This is idempotence at work, you could redeploy this same template 100 times and you'd end up with the same result. Now, let's try deploying to a new resource group. Note that we've changed $rg1 to $rg2 to reflect a different resource group, we're also generating a new DNS label as that is a resource that must be globally unique.

 $dnsLabelPrefix2 = "myvm-$(Get-Random)"
New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg2 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix2 `
    -windowsOSVersion $windowsOsVersion

Once this is done you should see that the resources in resource group templateTest1 are unaffected, with all resources from this deployment going into templateTest2. This was probably a pretty obvious point, but I do this to illustrate that even though resource names are the same (VNet, virtual machine name, etc.) they are scoped to a resource group. Unless reources have a globally unique requirement (as the DNS name has in this case) resources in different resource groups can have the same name.

Now let's make a modifition to the template and redeploy it to the templateTest1 resource group. Open up the 101-vm-simple-windows template and let's add a subnet. Find the virtualNetworks resource and add a subnet called NewSubnet. Your resource block for the virtual network should look as follows (note the addition of NewSubnet).

     {
      "apiVersion": "2016-03-30",
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[variables('virtualNetworkName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": {
              "addressPrefix": "[variables('subnetPrefix')]"
            }
          },
          {
            "name": "NewSubnet",
            "properties": {
              "addressPrefix": "10.0.1.0/24"
            }
          }
        ]
      }
    },

Now let's redeploy the template.

 New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg1 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix `
    -windowsOSVersion $windowsOsVersion

Once that's complete, you should see a new subnet called NewSubnet created in the virutal network. Pretty straightforward, we added a new property to an existing resource and it showed up as expected. Go back into the 101-vm-simple-windows template and remove that block for the new subnet. Now, redploy the template once more.

 New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg1 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix `
    -windowsOSVersion $windowsOsVersion

If you take another look at the virtual network you'll see that the subnet we had added is no longer there. This is also expected. We've made a change to an object itself, so the Azure fabric will deploy the resource based on that specified configuration in the template, whether we've added or removed a property of the resource. Let's try one last thing out. Go back into the 101-vm-simple-windows template and remove the entire virtualNetwork resource and redeploy.

 New-AzureRmResourceGroupDeployment -Verbose `
    -ResourceGroupName $rg1 `
    -TemplateFile $templateFile `
    -adminUsername $adminUserName `
    -adminPassword $password `
    -dnsLabelPrefix  $dnsLabelPrefix `
    -windowsOSVersion $windowsOsVersion

In this case you'll see that nothing has changed on the virtual network resource itself. Omitting the resource won't remove it, it will just omit it from any modifications.

Finally, let's clean everything up so we don't leave resources running unessecarily.

 Remove-AzureRmResourceGroup -ResourceGroupName $rg1 -Force -Verbose
Remove-AzureRmResourceGroup -ResourceGroupName $rg2 -Force -Verbose

Key takeaways

So after going through all this, what did we learn and what's important? First off, template idempotence is scoped to the resource group level. If the resource group a template is deployed to is changed, the scope of the template changes along with it. You can redeploy the same template over and over and nothing will change and no additional resources will be created or modified. Next, modifying, adding or removing a property of a resource will make that change to the resource. Thus, care should be taken when making changes within a resource so that you don't unintentionally modify configuration of a resource. Finally, omission of a resource in the template will have no impact on the resource itself, the Azure Resource Manager simply won't make any changes to that resource since it's omitted. Partial template deployments will only impact the resources specified in the template. Any resources outside those specified will be unaffected.

I hope this gives you a good understanding of idempotence and the scope of modifications when it comes to Azure Resource Manager templates.