Telerik blogs

You will recreate your application’s resources multiple times. You can do that with the traditional Azure Resource Management (ARM) templates and the newer Bicep language.

In previous posts in this series, I’ve walked through creating several Azure resources, all of which are required to create a three-tier, cloud-native application: an Azure SQL Database, an App Service (with accompanying App Service Plans) and a Managed Identity.

The reality is that, over your development process, you’re not going to create resources like these once. For various reasons, you’ll destroy and recreate your application’s resources multiple times. And, eventually, you’re going to need to create equivalent resources when you deploy your application to production.

There are two tools that will let you successfully recreate and deploy those resources: the traditional Azure Resource Management (ARM) templates and the (relatively speaking) new kid on the block, the Bicep language. Of the various Azure tools I’ve used in this series, only App Registrations aren’t supported by ARM templates or the Bicep language because registrations are not considered an Azure Resource (they are, instead, a way to create an identity in Microsoft Entra). For creating/recreating App Registrations, you’ll need to use a PowerShell or Azure CLI script.

By the way, since Microsoft refers to Bicep as a language, in this post I’ll refer to anything I create with Bicep as a program to distinguish it from an ARM template, though Microsoft’s documentation refers to “Bicep files” and “Bicep templates.”

Introducing ARM Templates and the Bicep Language

An ARM template is a JSON document that, when fed back to Azure will create or modify an existing resource. The Bicep language provides a succinct way to generate those JSON documents because running a Bicep program generates an ARM template. Not only is Bicep “transpiled” into an ARM template, as you’ll see, ARM templates can be “decompiled” into Bicep programs.

As I write this, ARM templates have one major advantage over Bicep: It’s easy to get the ARM template for any resource that you’ve already created. To generate an ARM template for any existing resource, first surf to your resource in the Azure Portal. Then, in the menu on the left side, drill down through the Automation node to Export template to view the ARM template that describes the resource.

To save your template into a file that you can store in GitHub or in your source control management system (or just drop into some folder), use the Download option in the menu at the top of the page where you’re viewing your resource’s template. Your template file is downloaded as a zip file that contains two files: template.json and parameters.json (more about that parameters.json file later in this post).

Your first look at an ARM template will tell you that reading or writing an ARM template is not for the faint of heart. As an example, here’s the ARM template for the simplest of the resources I’ve discussed so far, a Managed Identity (more interesting resources—database servers, for example—are much scarier):

{
 "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "userAssignedIdentities_WarehouseDBMgmt_name": {
            "defaultValue": "WarehouseDBMgmt",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
            "apiVersion": "2023-07-31-preview",
            "name": "[parameters('userAssignedIdentities_WarehouseDBMgmt_name')]",
            "location": "canadacentral"
        }
    ]
}

The equivalent Bicep template would look like this (in the spirit of Bicep, I’ve made some of the names used in the ARM template more succinct):

param identityName string = 'WarehouseDBMgmt'

resource WarehouseDBMgmtIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = {
  name: identityName
  location: 'canadacentral'
}

Microsoft provides a couple of tools for decompiling ARM templates to Bicep. The Bicep playground page will let you upload an ARM template and will decompile the template into the equivalent Bicep code (I leaned on it to create the Bicep version of my Managed Identity example). You can also use PowerShell or Azure CLI commands to generate a Bicep file from an ARM template. These examples decompile an ARM template in a file called WarehouseDBAcess.json:

bicep decompile WarehouseDBAccess.json
az bicep decompile –file WarehouseDBAccess.json

Those commands will generate a Bicep file with the same name as the ARM template file but with the extension .bicep (i.e., WarehouseDBAccess.bicep).

At the time of writing, there are some features of ARM templates that aren’t yet supported for Bicep. ARM templates, for example, support a what-if operation that lets you preview the results of deploying an ARM template without actually deploying the resulting resource. That operation is not currently available in Bicep.

In this post, I’m focusing on using ARM templates and Bicep to recreate single resources in development and/or production so I’m not going to get into the tools you’d use to create a complex deployment involving multiple resources (though I’ll wave at them in the Next Steps section at the end of this post).

Working with Parameters

My sample ARM template for my Managed Identity includes parameters that let me provide the name for my Managed Identity when I execute the template (and, because I decompiled that template into Bicep, so does my sample Bicep file). However, if I were planning to use my Managed Identity template to recreate the identity in production then I might also want to change the region the new identity name is assigned to.

One option for handling that is to create a separate ARM template or Bicep program for my production “redeployment,” giving me one file for development and another for production. This option can generate some maintenance overhead because whenever I make a change to the development file, I’ll need to make a similar change to the production file.

Another option is to, at the top of your ARM template or Bicep program, set up parameters for all the properties that you want to change between production and development (similar to the way the name of my Managed Identity is handled in my example). You can then use those parameters in the body of your template/program to control how the resource is created. Using parameters assumes that the two versions of the resource are sufficiently alike that the resulting “parameterized” template/program is easy to read and maintain (well … as far as any ARM template is easy to read).

Parameters do offer one additional advantage over creating multiple files: Parameters effectively centralize the differences between the two versions of your resource into the list of parameters. That difference shows up in two places:

  • The UIs available for deploying ARM templates often provide a way to navigate to your ARM template’s parameters.
  • Both ARM templates and Bicep provide a way to store your parameter values in a separate file that’s used when the resource is generated.

The original ARM template generated for my Managed Identity contained only one parameter: the identity’s name. You can see the definition of the name parameter in the parameters property of my ARM template and as a param type in my Bicep program. If I want to add another parameter to support changing the region when recreating this identity in production I can use those initial parameters as a guide.

By the way, if you’re going to edit ARM templates, Microsoft recommends that you use Visual Studio Code with the Azure Resource Manager Tools; Microsoft provides “equivalent” editing experiences for Bicep in both Visual Studio and Visual Studio Code.

Here’s a revised version of my ARM template (created by copying and pasting the existing parameters) that both defines and uses a new region parameter for my Managed Identity:

{
 "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "userAssignedIdentities_WarehouseDBMgmt_name": {
            "defaultValue": "WarehouseDBMgmt",
            "type": "String"
        },
        "userAssignedIdentities_Region_name": {
            "defaultValue": "canadacentral",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
            "apiVersion": "2023-07-31-preview",
            "name": "[parameters('userAssignedIdentities_WarehouseDBMgmt_name')]",
            "location": "[parameters('userAssignedIdentities_Region_name')]"
        }
    ]
}

And, in Bicep:

param identityName string = 'WarehouseDBMgmt'
param region string = 'canadacentral'

resource WarehouseDBMgmtIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = {
    name: identityName
    location: region
}

Managing Parameters

If you’re going to allow the user running your template to provide values at “deployment time,” then you’ll want to exercise some control to so that the user can only enter valid values. With parameters in an ARM template you can declare JSON properties to specify default values, minimum/maximum values or a list of approved values for the user to select from, among other options. In Bicep, you can provide equivalent control over input using decorators applied to your parameters.

While these input controls verify the user only enters valid values, they prevent a user from entering wrong values. You can address that by creating parameter files which let you specify the values for the parameters in your ARM template or Bicep program in a separate file for each deployment (i.e., one parameter file with the values for your development resources and a second parameter file with values for your production resources).

By the way: In both ARM templates and Bicep programs you can also define variables to, for example, hold calculated values or values used in several place in an ARM templates (again, you can use an existing template as a guide to defining and using your own variables) or Bicep programs.

Parameter Files in ARM Templates

This is where that parameters.json file, downloaded with your ARM template, becomes useful. When you execute your ARM template, the values in that parameters.json file will override the default values set in the ARM template. A parameters.json file for my Managed Identities example with values for my name and region parameter might look like this:

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

The tools used for deploying an ARM template will override the default values provided in your ARM template with values found in a parameters file (which doesn’t have to be called parameters.json, by the way, though that’s the name I’ll continue to use).

Any particular ARM template will look only for the parameters it needs in the parameters file you provide at deployment time. That means that you can use a single parameters file to hold the parameters for multiple ARM templates. Which also means that, if you use identical names for the parameters defined in your various ARM templates (e.g., the parameter specifying the region is always called region in every template), you can share parameter values among all the templates using any parameters file.

All the tools for generating a resource from an ARM template require you to pick which parameters file you want to use when you recreate/redeploy your resource. As a result, you’ll probably end up with at least two parameter files: one holding your production values and one holding your development values.

Parameter Files in Bicep Programs

With Bicep, parameters are kept in a .biceparam file. In addition to parameter names and values, the Bicep parameters file includes a using statement that ties the file to a particular .bicep file. You can use Bicep parameter files with ARM templates (just set the using statement to “parameters.json”). If you’re thinking about migrating existing ARM templates to Bicep, parameter files might be a good place to start (the tools I mentioned earlier for decompiling ARM templates into Bicep work equally well with parameter files).

A parameters file for my Bicep Managed Identities example might look like this (this example assumes the bicep file is in the same folder as this parameters file):

using './ WarehouseDBAccess.bicep'

param identityName = 'WarehouseDBMgmt'
param region = 'canadacentral'

Tying a parameters file to a specific Bicep program reflects one of the major differences between Bicep and ARM templates. In Bicep, you can create modules for each resource you want to create and then assemble those modules into a single deployment. This allows you to create one deployment tied to a parameters file with your development values and a second deployment tied to a parameters file with your production values. Those two deployments might use many or most of the same modules.

Deploying

You can deploy your resource from the same Automation | Export template page where you downloaded the resource’s ARM template. Clicking the Deploy option at the top of the page opens a new page that looks very much like the wizard you used when you first created that resource. Once that page is opened, you can use the Edit parameters option at the top of the page to tweak your parameters displayed and click the Save button to commit your change. You can then click the Review + create button at the bottom of the page, and execute your ARM template.

In this scenario, since the resource already exists (you surfed to its page, after all), the template will update the resource with whatever changes you’ve made to the parameters.

It’s more likely, however, that you’ll want to download that ARM template (there’s a download button at the top of the page) and then use the template later to recreate your resource (or decompile it into a Bicep program that you’ll use later).

Often, though, you won’t want to deploy a single template (especially true if you’re moving your application to production—you’ll want to deploy all the resources your application depends on). If you’ve been clever about assigning resources to resource groups, you can surf to your resource group and use the resource group’s Automation | Export template option to generate an ARM template for all the resources in the group. Among other benefits, the resulting monolithic template will centralize the parameters from the individual templates into a single parameters section.

Of course, some of those resources depend on other resources, so the order of creation matters (the Managed Identity must be created before it can be applied to the App Service, for example). One of the benefits of using ARM templates is that it will automatically sequence creating the resources in a single deployment for you (it will also deploy as many resources in parallel as it thinks is possible).

Since Bicep programs generate ARM templates, you also get those benefits when using Bicep.

Deploying Your Template

Once your template is created, you can deploy the resource it describes using the Azure Portal’s Deploy Custom Template tool, PowerShell or the Azure CLI. In the following examples, I’ve always used an ARM template in a .json file, but all of the tools work equally well with Bicep code in a .bicep file.

In the Azure Portal

To deploy your template from the Azure Portal, you can use the Custom Deployment wizard. To start the wizard, in the portal’s search textbox at the top center of the page, search for and select Deploy a custom template. When the first page of the wizard appears:

  1. Click the Build your own template in the editor link to open the Edit template window.
  2. In Edit template window, click the Load file choice in the menu across the top of the editor window.
  3. In the resulting Open dialog, surf to and select your template and then click the Open button.
  4. When your template displays in the page’s editor window, you can review and update any parameters or use your parameters file in a later step. (You can drill down through the parameters option to the left of the editor window to have your cursor in the editor window moved to each parameter.)
  5. Click the Save button in the lower left corner to return to the first page of the Custom Deployment wizard.
  6. To use your parameters file, click the Edit parameters option at the top of the page to open an Edit parameters page.
  7. Use the Load file option to upload your parameters file.
  8. Click the Save button in the lower left corner to return to the first page of the Custom Deployment wizard.
  9. On the Custom deployment page, select a resource group from the Resource group dropdown list (or create a new one).
  10. Click the Review + create button to go to the last page of the Custom deployment wizard.
  11. At the bottom of that page, click the Create button to create your resource.

PowerShell

If you’re using PowerShell from the desktop to create your resource, then you’ll need to first open PowerShell as an administrator and add the Azure cmdlets to your PowerShell environment with this statement (you’ll need to respond with a y to one or more prompts generated by this command):

Install-Module -Name Az -AllowClobber -Scope CurrentUser

Once you have the cmdlets installed and before you can deploy using your template, you must use the Connect-AZAccount cmdlet to connect to your Azure account. Running that cmdlet with no parameters will pop up a dialog that will let you log into your Azure account and cause any subsequent Azure cmdlets commands to work with your Azure tenant.

You skip those two steps by using PowerShell from the Azure Portal’s Cloud Shell which already has the Azure cmdlets installed and is already connected to your Azure account.

To open the Cloud Shell, in the portal page’s title bar click on the icon that looks faintly like a command prompt with a cursor: >_. If this is your first time, you’ll be asked to create storage and file system spaces—create both.

You will, however, have to upload your template file to the Cloud Shell’s storage (select the Upload choice from the Cloud Shell’s Manage files menu).

In either the Cloud Shell or PowerShell desktop, you execute your ARM template or Bicep file with the New-AzResourceGroupDeployment cmdlet, passing a name for the deployment, the name of a resource group, the name of your template file and (optionally) the name of your parameters file.

This example assumes that the current folder holds my ARM template (which I’ve enclosed in single quotes because there are spaces in the file name) and uses the command’s TemplateParameterFile parameter to provide a parameters file:

New-AzResourceGroupDeployment `
   -Name PowershellDeploy `
   -ResourceGroupName WarehouseMgmt `
  -TemplateFile 'arm managed identity.json'
  -TemplateParameterFile parameters.json

Azure CLI

To use the Azure CLI from your desktop, you’ll first need to download and install it.

Once you’ve installed the Azure CLI and before you can deploy using your template, you must use the az login command to connect to your Azure account. Running that command will pop up a dialog that will let you log into your Azure account and direct any subsequent Azure CLI commands to your tenant.

You can skip those two steps by using the Azure CLI from the Azure Portal’s Cloud Shell which already has the Azure CLI installed and is already connected to your Azure account. To open the Cloud Shell, in the portal page’s title bar click on the icon that looks faintly like a command prompt with a cursor: >_. If this is your first time, you’ll be asked to create storage and file system spaces—create both.

You will have to upload your template file to the Cloud Shell’s storage (select the Upload choice from the Cloud Shell’s Manage files menu).

In either the Cloud Shell or PowerShell desktop, you execute your ARM template or Bicep file using the az deployment group command to deploy your resource to an Azure resource group. The following example assumes the template file is in the current folder (with the Azure CLI, the template file name cannot have spaces in it). This example uses the command’s parameters parameter to specify the parameters file to use:

az deployment group create `
  --resource-group WarehouseMgmt `
   --template-file armmanagedidentity.json"
  -- parameters @parameters.json

Next Steps

For a deployment that involves a lot of resources, creating a single monolithic ARM template or Bicep program from a resource group will make maintaining those large files challenging (even if Bicep makes the code more succinct).

If you’re using ARM templates directly (i.e., without Bicep), you can create a “master template” that invokes other templates (and also lets you pass values between those “child” templates).

With Bicep you can create a module for each resource and then assemble them into a single deployment, as I mentioned earlier.

You can further simplify deploying multiple resources by using Deployment Stacks to manage a group of resources as a single unit.

You can also share your ARM templates with other users by setting your templates up as a resource themselves using template specs. You can share your Bicep modules with other users in a Bicep registry.

Now that you have some tools for managing the resources that make up your application, we can look at making your application more secure with the Azure Key Vault. That’s my next post.


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is both the author of the Coding Azure series and the instructor for Coding Azure in the Classroom. Peter’s company provides full-stack development from UX design through object modeling to database design. Peter holds multiple certifications in Azure administration, architecture, development and security and is a Microsoft Certified Trainer.

Related Posts

Comments

Comments are disabled in preview mode.