Diagnosing Azure Resource Manager template validation errors

In previous post I have provided intro to Azure Resource templates. While performing deployment of the template you might run into situation where Azure is returning quite a cryptic message similar to one one the screenshot below.
From the error you will get few pieces of information:
  • Name of the template: azuredeploy
  • Template has failed validation procedure
  • You can check details with provided tracking Id which is some Guid
Details we are looking for can be found in Activity Log of Resource Group to which deployment was done and there are few ways we can get it. Activity Log is a place where actions related to resources are logged (in this case we will be looking for logs on Resource Group level) e.g.:
  • Deletion of resources
  • Deployments
  • Starting and stopping of resources
The logs include such information as:
  • Timestamp
  • Principal which initiated event
  • JSON object which describes change in details

Break something!

To show you sample error like one on the screenshot above we will break something to trigger validation error. We will use code from the previous post which is located in GitHub repository.

Clone the repository and open azuredeploy.parameters.json file. To break it change value of the storageAccountName parameter to provision-warm-template. Now you can try to deploy the template with the instruction from the previous post or the GitHub repository - as a result you should get error similar to one on the screenshot above.

Using Azure Portal

In Azure Portal you can view Activity Log for deployment on Resource Group level by navigating to the group where you tried to deploy resources and choosing Activity Log blade.


You should see list of recent activities on the Resource Group, including our failed deployment which is marked with red icon and Failed status. After clicking on the failed deployment record you should see panel with details as on screenshot below.


On the Summary tab you will find pretty much the same information as in the console. The interesting part can be found in JSON tab - here you will find JSON object with the details of the deployment request, the piece of information we are looking for is contained in properties.statusMessage property. Here you will find stringified JSON object, similar to one in the snippet.
{\"error\":{\"code\":\"InvalidTemplateDeployment\",\"message\":\"The template deployment 'azuredeploy' is not valid according to the validation procedure. The tracking id is '0fa3c66c-6b01-4f42-aadf-0a886c926d35'. See inner errors for details.\",\"details\":[{\"code\":\"PreflightValidationCheckFailed\",\"message\":\"Preflight validation failed. Please refer to the details for the specific errors.\",\"details\":[{\"code\":\"AccountNameInvalid\",\"target\":\"provision-warm-template\",\"message\":\"provision-warm-template is not a valid storage account name. Storage account name must be between 3 and 24 characters in length and use numbers and lower-case letters only.\"}]}]}}
In this case we can find information about actual reason of failure at the end of this longish string - long story short, name of the Storage Account does not pass the validation.

Using Azure CLI

We can get same data with Azure CLI just by providing tracking Id from the error.
az monitor activity-log list --correlation-id 0fa3c66c-6b01-4f42-aadf-0a886c926d35
Instead of Guid provided as an value for correlation-id parameter, provide tracking id value from the error. Running the command will result with JSON object which includes error details in properties.statusMessage. You probably noticed that output JSON is verbose and does include lots of not related information which obfuscates things. It would be great to be able to extract only relevant information. Thankfully Azure CLI has query argument which allows to extract only the information we are interested in from the output of the command. Lets see how it works and run following command:
az monitor activity-log list `
    --correlation-id 0fa3c66c-6b01-4f42-aadf-0a886c926d35 `
    --query "[?level=='Error'].properties.{Entity: entity, StatusCode: statusCode, StatusMessage: statusMessage}" `
    --output yaml
The output of the command should be look like that:
- Entity: /subscriptions/975874d0-9f27-4c97-8464-701c68575c6b/resourcegroups/my-infra-rg/providers/Microsoft.Resources/deployments/azuredeploy
  StatusCode: BadRequest
  StatusMessage: '{"error":{"code":"InvalidTemplateDeployment","message":"The template
    deployment ''azuredeploy'' is not valid according to the validation procedure.
    The tracking id is ''0fa3c66c-6b01-4f42-aadf-0a886c926d35''. See inner errors
    for details.","details":[{"code":"PreflightValidationCheckFailed","message":"Preflight
    validation failed. Please refer to the details for the specific errors.","details":[{"code":"AccountNameInvalid","target":"provision-warm-template","message":"provision-warm-template
    is not a valid storage account name. Storage account name must be between 3 and
    24 characters in length and use numbers and lower-case letters only."}]}]}}'
As you can see it is much easier to understand what is actual problem with deployment just by looking at StatusMessage property of resulting YAML. Lets dissect the command and see how it works.

az monitor activity-log - first piece of command is responsible for selecting Azure resource type against which command will be run. In this case we would like to ingest activity logs which are part of Azure Monitor - thus we use monitor activity-log. For other types of resources you can use other parameter names e.g. account for subscription or group for resource groups.

list - tells Azure to get... list (surprise :) of all resources of specific type (defined in the first piece of command) executing principal has access to.

--correlation-id <Guid> - this is one of optional parameters for az monitor activity-log list, which is responsible for getting only these activity log records which are assigned to specific correlation Id.

--query <JMESPath Query> - this is the most interesting part of the command. It accepts so called JMESPath query which allows to filter out result and select only the fields we are interested in.
Lets take a closer look at the query we have used in example above:

First note that for Azure CLI JMESPath queries have to be provided in double quotes - it is quite important to remember about that, in some cases you will not get any syntax error but instead of that results my be ambiguous.
I have split above query into three pieces: filter, path and field selectors:
  • Filter - [?level='Error'] - the first piece is surrounded with square braces which mean that we are dealing with JSON array. Inside of braces with have expression starting with question mark - it is responsible for filtering out object which do not meet the condition - level property of the object must be equal to Error. To sum up, the filter expression here will go through array of objects and filter out these object where level property has value other than Error.
  • Path - this part of query string tells that only properties property of the object should be returned.
  • Field Selectors - at the end in curly braces there are field selectors, they are responsible for selecting the fields to be returned. Imagine object with ten properties, out of which you are interested in two (similar as in our case), with this syntax you are able to tell Azure to return only selected set of fields and give each of them alias name. For each field the syntax is following: <alias>: <name of the field in original object>.

--output yaml - the last parameter is responsible for defining the output format of the data to by YAML. As you probably noticed by default Azure CLI is outputting the data in JSON format. Other commonly used value for this parameter is table. Full list of possible values can be found in https://docs.microsoft.com/pl-pl/cli/azure/format-output-azure-cli

With that knowledge you should be able to build simple yet powerful queries for filtering out data returned by Azure CLI as the --query parameter is available for all the commands. No go and experiment with query syntax :)

More reading

Comments