Azure nested template deployment: Using template e

2019-06-14 04:30发布

问题:

In an attempt to make life easier (in the long run), I'm trying to use properties.template, as opposed to the well documented properties.templateLink. The former has very little documentation by passing the contents of child.json template file into the parent.json template, as a template' parameter.

From the MS documentation for Microsoft.Resources/deployments:

The template content. You use this element when you want to pass the template syntax directly in the request rather than link to an existing template. It can be a JObject or well-formed JSON string. Use either the templateLink property or the template property, but not both.

In my parent template, I am declaring the parameter childTemplates and referencing it in properties.template:

"parameters": {
    "childTemplates": {
      "type": "object",
      "metadata": {
        "description": "Child template"
      }
    }
}

other stuff...


"resources": [
  {
    "name": "[concat('linkedTemplate-VM-Net-',copyIndex(1))]",
    "type": "Microsoft.Resources/deployments",
    "apiVersion": "2017-06-01",
    "dependsOn": [],
    "copy": {
      "name": "interate",
      "count": "[parameters('vmQty')]"
    },
    "properties": {
      "mode": "Incremental",
      "template": "[parameters('childTemplates')]",
      "parameters": {
        "sharedVariables": { "value": "[variables('sharedVariables')]" },
        "sharedTemplate": { "value": "[variables('sharedTemplate')]" },
        "artifactsLocationSasToken": { "value": "[parameters('artifactsLocationSasToken')]" },
        "adminPassword": { "value": "[parameters('adminPassword')]" },
        "copyIndexValue": { "value": "[copyIndex(1)]" }
      },
      "debugSetting": {
        "detailLevel": "both"
      }
    }
  }
],

I then pass the child template to New-AzureRmResourceGroupDeployment -TemplateParameterObject to deploy the parent template:

$TemplateFileLocation = "C:\Temp\templates\parent.json"
$JsonChildTemplate = Get-Content -Raw (Join-Path ($TemplateFileLocation | Split-Path -Parent) "nestedtemplates\child.json") | ConvertFrom-Json

$TemplateParameters = @{
    childTemplates = $JsonChildTemplate
    ...Other parameters...
}

New-AzureRmResourceGroupDeployment -TemplateParameterObject $TemplateParameters

This produces the following error:

Code    : InvalidTemplate
Message : The nested deployment 'linkedTemplate-VM-Net-1' failed validation: 'Required property '$schema' not found in JSON. Path 'properties.template'.'.
Target  : 
Details : 

If I look at $JsonChildTemplate, it gives me:

$schema        : https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#
contentVersion : 1.0.0.0
parameters     : @{sharedVariables=; sharedTemplate=; vhdStorageAccountName=; artifactsLocationSasToken=; adminPassword=; copyIndexValue=}
variables      : @{seqNo=[padleft(add(parameters('copyIndexValue'),3),3,'0')]; nicName=[concat('NIC-',parameters('sharedVariables').role,'-', variables('seqNo'),'-01')]; 
                 subnetRef=[parameters('sharedVariables').network.subnetRef]; ipConfigName=[concat('ipconfig-', variables('seqNo'))]}
resources      : {@{apiVersion=2016-03-30; type=Microsoft.Network/networkInterfaces; name=[variables('nicName')]; location=[resourceGroup().location]; tags=; dependsOn=System.Object[]; 
                 properties=}}
outputs        : @{nicObject=; vmPrivateIp=; vmNameSuffix=; vmPrivateIpArray=}

To me, it looks like the $schema is there.

I have also tried removing | ConvertFrom-Json with the same error.

Above, I am showing the latest API version, but I have tried with others such as 2016-09-01, just in case there's a bug.

In my search for a solution, I found this issue on GitHub. The recomendation is to remove $schema and contentVersion, although this flies in the face of the error. I tried this with the following:

Function Get-ChildTemplate
{
    $TemplateFileLocation = "C:\Temp\templates\nestedtemplates\child.json"

    $json = Get-Content -Raw -Path $TemplateFileLocation | ConvertFrom-Json 

    $NewJson = @()
    $NewJson += $json.parameters
    $NewJson += $json.variables
    $NewJson += $json.resources
    $NewJson += $json.outputs

    Return $NewJson | ConvertTo-Json
}

$JsonChildTemplate = Get-ChildTemplate
$TemplateParameters = @{
    childTemplates = $JsonChildTemplate
    ...Other parameters...
}

$JsonChildTemplate returns:

[
    {
        "sharedVariables":  {
                                "type":  "object",
                                "metadata":  "@{description=Object of variables from master template}"
                            }...

My guess is that I have done something wrong passing child.json's contents to New-AzureRmResourceGroupDeployment. That or it's not actually possible to do what I'm trying to do.

P.S.

get-command New-AzureRmResourceGroupDeployment

CommandType     Name                                               Version    Source                                                                                                              
-----------     ----                                               -------    ------                                                                                                              
Cmdlet          New-AzureRmResourceGroupDeployment                 4.1.0      AzureRM.Resources

回答1:

First of all, what you are doing makes 0 sense what so ever, that being said, lets try to help you.

  1. Try splatting. so do New-AzureRmResourceGroupDeployment ... @TemplateParameters instead of what you are doing. (no idea, but somehow it works better in my experience)
  2. If that doesn't work directly try simplifying you nested template to the bare minimum and see if it works if it does, check if your nested template is fine.
  3. Try creating a deployment with -Debug switch and see where that goes.
  4. Try the same deployment using Azure Cli (maybe it converts json to input object in a proper way)
  5. Skip items 1-4 and do it the proper way. I would advice never do preprocessing\in flight generation of ARM Templates. They have enough of features already to accomplish anything if you are smart hacky enough. I have no idea what you are trying to achieve but I can bet my life on it you don't need that monstrosity you are trying to create

small template example:

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

EDIT: I dug a bit more and found a solution. one way to do it would be using the json() function of the arm template that accepts a string and converts to valid json.

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "inp": {
            "type": "string"
        }
    },
    "resources": [
        {
            "name": "NestedDeployment1",
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2015-01-01",
            "properties": {
                "mode": "Incremental",
                "template": "[json(parameters('inp'))]",
                "parameters": {}
            }
        }
    ]
}

To deploy use something like this:

New-AzureRmResourceGroupDeployment ... -inp ((get-content path\to.json -raw) -replace '\s','')
# we minify the string so it gets properly converted to json

This is a bit of a hack, but the problem lies with how powershell converts your input to what it passes to the template, and you cannot really control that.

Another way to do that: (if you need output you can add another parameter)

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "input-param": {
            "type": "object"
        },
        "input-resource": {
            "type": "array"
        }
    },
    "resources": [
        {
            "name": "NestedDeployment1",
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2015-01-01",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": "[parameters('input-param')]",
                    "resource": "[parameters('input-resource')]"
                },
                "parameters": {}
            }
        }
    ]
}

and deploying like so:

New-AzureRmResourceGroupDeployment -ResourceGroupName zzz -TemplateFile path\to.json -input-param @{...} -input-resource @(...)

ps. don't mind walter, each time he says something can't be done or is impossible it actually is possible.