Skip to content

Instantly share code, notes, and snippets.

@ahejlsberg
Last active May 7, 2020 02:41
Show Gist options
  • Save ahejlsberg/b62f603f8004210d4d1abe528238a235 to your computer and use it in GitHub Desktop.
Save ahejlsberg/b62f603f8004210d4d1abe528238a235 to your computer and use it in GitHub Desktop.
ARM JSON ideas

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.

Consistent namespacing

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').

Succinct symbolic name references

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')).

List comprehensions

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.

An example

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]"]
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment