Getting Started with ARM Templates

Getting Started with ARM Templates

Infrastructure as Code (IaC) is a modern approach to managing and provisioning cloud resources, enabling developers and operations teams to automate and streamline their infrastructure deployment and configuration. Azure Resource Manager (ARM) templates play a crucial role in implementing IaC in Azure environments, providing a declarative syntax to define, configure, and manage Azure resources consistently and predictably. With ARM templates, we can simplify infrastructure management, reduce human error, and ensure that your Azure projects are scalable and maintainable. In this guide, we will examine the essential elements of ARM templates by addressing a straightforward task: provisioning an Azure Storage Account.

Pre-requisites

ARM Template Anatomy

Here is an empty ARM template:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {  },
  "variables": {  },
  "functions": [  ],
  "resources": [  ],
  "outputs": {  }
}

The sections below will not have a detailed explanation:

  • $schema: Location of the JSON schema file that describes the version of the template language.

  • contentVersion: Version of the template. We can provide any value for this element.

Resources

This section defines the resources that are deployed or updated. Create a file named template.json with the following content:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {  },
  "variables": {  },
  "functions": [  ],
  "resources":  [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "myuniquestorage001",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "tags": {},
      "sku": {
        "name": "Standard_GRS"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ],
  "outputs": {  }
}

Most resources possess a set of common properties, such as:

  • Name: Name of the resource.

  • Type: The type of resource to deploy. This value is a combination of the namespace of the resource provider and the resource type (separated by a /).

  • ApiVersion: The version of the REST API used for creating the resource, which determines the available properties.

  • Tags: Tags that are associated with the resource.

  • Location: Supported geographical locations for the provided resource. We typically deploy resources to the same resource group in which the deployment is created.

  • DependsOn: ARM templates need the manual creation of resource dependencies. These dependencies dictate the sequence in which Azure deploys the resources.

  • Properties: Resource-specific configuration settings.

  • Kind: Some resources allow a value that defines the type of resource you deploy.

  • SKU: Some resources allow values that define the SKU to deploy.

To deploy the template, a resource group is required:

az group create -l eastus -n MyResourceGroup

After creating the resource group, execute the following command:

az deployment group create --resource-group MyResourceGroup --template-file .\template.json

Navigate to the created resource group in the Azure portal to view the deployment execution:

Parameters

Parameters enable you to provide various values to the ARM template during the deployment:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {     
    "storageAccountName": {
      "type": "string",
      "metadata": {
          "description": "The name of the storage account."
      }
    } 
  },
  "variables": {  },
  "functions": [  ],
  "resources":  [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "tags": {},
      "sku": {
        "name": "Standard_GRS"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ],
  "outputs": {  }
}

To pass inline parameters, you can execute a command like this:

az deployment group create --resource-group MyResourceGroup --template-file .\template.json --parameters storageAccountName='myuniquestorage002'

Instead of using inline values for parameters in your script, you can utilize a JSON file containing the parameter values. Create a parameter.json file with the following content:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "value": "myuniquestorage003"
    }
  }
}

To pass a local parameter file, use @ to specify a local file:

az deployment group create --resource-group MyResourceGroup --template-file .\template.json --parameters '@parameters.json'

Variables

Like parameters, variables can contain values but also template expressions. They are often used to simplify templates by minimizing the usage of template expressions. Let's modify our script to automatically generate a name based on a prefix:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {     
    "storageAccountPrefix": {
      "type": "string",
      "metadata": {
          "description": "The prefix of the storage account."
      }
    } 
  },
  "variables": { 
    "storageAccountName": "[concat(toLower(parameters('storageAccountPrefix')), uniqueString(resourceGroup().id))]"
   },
  "functions": [  ],
  "resources":  [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "tags": {},
      "sku": {
        "name": "Standard_GRS"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ],
  "outputs": {  }
}

We can utilize all the functions mentioned here. Execute the following command:

az deployment group create --resource-group MyResourceGroup --template-file .\template.json --parameters storageAccountPrefix='mystorage'

Outputs

The outputs section defines values and information returned from the deployment. Update the template as follow:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {     
    "storageAccountPrefix": {
      "type": "string",
      "metadata": {
          "description": "The prefix of the storage account."
      }
    } 
  },
  "variables": { 
    "storageAccountName": "[concat(toLower(parameters('storageAccountPrefix')), uniqueString(resourceGroup().id))]"
   },
  "functions": [  ],
  "resources":  [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "tags": {},
      "sku": {
        "name": "Standard_GRS"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ],
  "outputs": {  
      "endpoints": {
      "type": "object",
      "value": "[reference(variables('storageAccountName')).primaryEndpoints]"
    }
  }
}

Deploy the template, and you should see an output similar to this:

... 
"outputs": {
      "endpoints": {
        "type": "Object",
        "value": {
          "blob": "https://mystorageeksid6zaqw34i.blob.core.windows.net/",
          "dfs": "https://mystorageeksid6zaqw34i.dfs.core.windows.net/",
          "file": "https://mystorageeksid6zaqw34i.file.core.windows.net/",
          "queue": "https://mystorageeksid6zaqw34i.queue.core.windows.net/",
          "table": "https://mystorageeksid6zaqw34i.table.core.windows.net/",
          "web": "https://mystorageeksid6zaqw34i.z13.web.core.windows.net/"
        }
      }
    },
...

Functions

Functions enable you to create complex expressions that we would prefer not to repeat throughout the template. Let's define a function to generate unique names based on a prefix:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {     
    "storageAccountPrefix": {
      "type": "string",
      "metadata": {
          "description": "The prefix of the storage account."
      }
    } 
  },
  "variables": { 
    "storageAccountName": "[mynamespase.uniqueName(parameters('storageAccountPrefix'))]"
   },
  "functions": [ 
   {
    "namespace": "mynamespase",
    "members": {
      "uniqueName": {
        "parameters": [
          {
            "name": "namePrefix",
            "type": "string"
          }
        ],
        "output": {
          "type": "string",
          "value": "[concat(toLower(parameters('namePrefix')), uniqueString(resourceGroup().id))]"
        }
      }
    }
  }
   ],
  "resources":  [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2019-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [],
      "tags": {},
      "sku": {
        "name": "Standard_GRS"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ],
  "outputs": {  
      "endpoints": {
      "type": "object",
      "value": "[reference(variables('storageAccountName')).primaryEndpoints]"
    }
  }
}

When defining a user function, there are some restrictions:

  • The function can't access variables.

  • The function can only use parameters that are defined in the function. When you use the parameters function within a user-defined function, you're restricted to the parameters for that function.

  • The function can't call other user-defined functions.

  • The function can't use the reference function or any of the list functions.

  • Parameters for the function can't have default values.

Deployment Modes

When deploying your resources, we can specify whether the deployment should be an incremental update or a complete update:

  • complete: Resource Manager deletes resources that exist in the resource group but aren't specified in the template.

  • incremental: Resource Manager leaves unchanged resources that exist in the resource group but aren't specified in the template.

The incremental mode is the default setting. To use the complete mode, execute a command like:

az deployment group create --resource-group MyResourceGroup --template-file .\template.json --parameters storageAccountPrefix='mystorage' --mode complete

Preview

To preview the changes that will occur, Azure Resource Manager offers the what-if operation:

az deployment group what-if --resource-group MyResourceGroup --template-file .\template.json --parameters storageAccountPrefix='mystorage

We can use --confirm-with-what-if to preview the changes and get prompted to continue with the deployment:

 az deployment group create --confirm-with-what-if --resource-group MyResourceGroup --template-file .\template.json --parameters storageAccountPrefix='mystorage'

In conclusion, ARM templates are a powerful tool for implementing Infrastructure as Code in Azure environments. By understanding the essential elements of ARM templates, such as resources, parameters, variables, outputs, and functions, we can effectively manage your Azure resources and enhance your cloud projects. Thanks, and happy coding.