Given that the ARM JSON format will serve as the "IL" for a new DSL and/or a JS/TS-based template generator library, it's important for the JSON format to provide a strong semantic foundation. I think there are several missing concepts at this point. In particular:
- No consistent symbolic namespacing of entities (parameters, variables, resources) declared in a template.
- No succinct syntax for symbolically accessing entities in expressions.
- Copy loops are extremely verbose and hard to get right.
- Array manipulation primitives are weak and there is no notion of list comprehensions.
I have a few ideas for extensions to the JSON format to address this.
Currently, the "parameters"
and "variables"
sections specify symbolic names for entities, but "resources"
sections do not. I suggest that it be possible to give symbolic names to resources, e.g.
{
...
"resources": {
"storage": {
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[concat(index, baseName)]",
...
}
"vm": {
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2015-06-15",
"name": "[concat('VM', uniqueString(resourceGroup().id))]",
"dependsOn": [resource("storage")],
}
}
...
}
Just like you can access parameters and variables using parameter('xxx')
and variable('xxx')
, so should you be able to reference resources by their symbolic names using resource('xxx')
.
It seems odd and unnecessarily verbose to require every reference to a parameter, variable, or resource to use parameter('foo')
instead of just foo
. I suggest allowing references to just use the symbolic name as long as it isn't ambiguous (i.e. as long as the name is unique among all entity names). For example, concat(accountName, '-', storageName)
instead of concat(parameter('accountName'), '-', variable('storageName'))
.
I suggest introducing list comprehensions as a simpler way to write repeated resources and array properties and expressions. The general format would be an array literal
["[for <name> in <array-expr>]", <element>]
where <name>
is an identifer, <array-expr>
is an expression of an array type, and <element>
is some JSON value that can use the identifier <name>
to reference the current element.
Here's a template with some examples of these features. Notice how list comprehensions make repeated constructs a lot simpler, how declared entities are referenced just by their symbolic name, and how namespacing makes it easy to reference a repeated resource in the output of the template. Of course, it's all still JSON with quotes and brackets and all, but semantically this is much closer to what a DSL would look like, and it would be a lot easier for a DSL-to-JSON translator to output this.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageCount": {
"type": "int",
"defaultValue": 2
},
"numberOfDataDisks": {
"type": "int",
"minValue": 0,
"maxValue": 16,
"defaultValue": 16,
"metadata": {
"description": "The number of dataDisks to create."
}
},
},
"variables": {
"baseName": "[concat('storage', uniqueString(resourceGroup().id))]",
"listOfNames": ["[for n in range(storageCount)]", "[concat(baseName, '-', n)]"],
"listOfObjects": ["[for name in listOfNames]", {
name: "[name]",
diskSize: 1024
}]
},
"resources": {
"storage": [
"[for index in range(storageCount)]", {
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-04-01",
"name": "[concat(index, baseName)]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {
"storageProfile": {
"dataDisks": [
"[for n in range(numberOfDataDisks)]", {
"diskSizeGB": 1023,
"lun": "[n]",
"createOption": "Empty"
}
]
}
}
}
],
"vm": {
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2015-06-15",
"name": "[concat('VM', uniqueString(resourceGroup().id))]",
"dependsOn": ["storage"],
}
},
"outputs": {
"storageEndpoints": {
"type": "array",
"input": ["[for s in storage]", "[s.primaryEndPoints.blob]"]
}
}
}