Deploying to Azure Without Writing JSON Arm Templates

Reading Time: 6 minutes (not a minute longer)

TLDR;

ARM templates are a great way to deploy to Azure, setting up a template that can deploy an entire environment from databases to web servers and load balancers to firewalls (damn you azure, ok, I’ll call it an nsg) is satisfying but writing and managing blobs of JSON is less satisfying. In this post, we look at what are the alternatives to writing ARM templates in JSON.

someone staring at their screen of JSON blobs in desperation

So, what are our options?

  • Create/Edit/Delete ourselves using Powershell/.Net/Python/Go/Java/Some Other SDK
  • Process something else (YAML?) into JSON
  • Generate the ARM using c#/Powershell/something else
  • 3rd party tools, (Terraform is the big daddy) / others include Sparkle Formation

Create/Edit/Delete ourselves using Powershell/.Net/Python/Go/Java/Some Other SDK

To deploy an ARM template, we take our JSON and we send it to the Azure REST API:

https://management.azure.com/subscriptions/{subscription-id}/resourcegroups/{resource-group-name}/providers/microsoft.resources/deployments/{deployment-name}?api-version={api-version}

Getting the JSON to the REST API might be done via Powershell’s New-AzureResourceGroupDeployment, but whatever happens the JSON ends up at the REST API. The REST API then parses the template and works out what it is it has to do. It figures out what the parameters, variables should be and where there are template functions it resolves those to the actual values. The template function [concat('hello', ' ', 'ed')] becomes hello ed or maybe helloed who knows really but we’ll find out when we have deployed (or it failed).

Once the resource group deployment API knows what it needs to create it calls the other Azure REST API’s to do the actual work, for example, if you have this resource in your JSON ARM template:

{ "apiVersion": "2017-12-01", "type": "Microsoft.Compute/virtualMachines", "name": "[variables('vmName')]", "location": "[parameters('location')]", "dependsOn": [ "updateIp" ], "properties": { "hardwareProfile": { "vmSize": "Standard_A1" }, "osProfile": { "computerName": "[variables('vmName')]", "adminUsername": "[parameters('adminUsername')]", "adminPassword": "[parameters('adminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "Canonical", "offer": "UbuntuServer", "sku": "16.04.0-LTS", "version": "latest" } }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" } ] } } }

Then this API is going to be sent a PUT request:

https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/myVM?api-version=2017-12-01

(See https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines/createorupdate)

With this body: { "location": "westus", "plan": { "publisher": "Canonical", "product": "windows-data-science-vm", "name": "UbuntuServer" }, "name": "vmNameVariableResult", "properties": { "hardwareProfile": { "vmSize": "Standard_A1" }, "storageProfile": { "imageReference": { "sku": "16.04.0-LTS", "publisher": "Canonical", "version": "latest", "offer": "UbuntuServer" }, "osDisk": { "caching": "ReadWrite", "managedDisk": { "storageAccountType": "Standard_LRS" }, "name": "myVMosdisk", "createOption": "FromImage" } }, "osProfile": { "adminUsername": "adminUsernameParameterValue", "computerName": "vmNameVariableResult", "adminPassword": "adminPasswordParameterValue" }, "networkProfile": { "networkInterfaces": [ { "id": "networkResourceId/nicNameVariableResult", "properties": { "primary": true } } ] } } } Whoa, I hear you say, that looks pretty similar, and you are right, they are pretty similar but what you get with ARM templates is a built-in way to say “give me 10 of these” or parametrise bits and bobs.

So here it is, ARM templates are designed by Microsoft for millions of customers, you are likely just one customer so if you can use one of the SDK’s to describe your infrastructure easier than with JSON templates then look into it.

If you go this route and develop something that is harder and more complex than JSON blobs then throw it away and use JSON ARM templates :)

Process something else (YAML?) into JSON

A slightly less daunting version of using one of the SDKs directly is to use some other config format and convert that into JSON. YAML is a popular framework and is used by AWS to deploy using CloudFormation, so there have been a few protest votes here to get YAML ARM templates:

https://feedback.azure.com/forums/281804-azure-resource-manager/suggestions/14885034-add-support-for-yaml-deployment-templates

With someone writing their own converter: https://github.com/ericksond/azure-arm-yaml

Even someone inside Microsoft thinks it is a good idea and wrote a JSON from YAML converter for some Azure resources:

https://github.com/Azure/azure-quickstart-templates/tree/master/elasticsearch

and the YAML version of the ARM template:

https://github.com/Azure/azure-quickstart-templates/blob/master/elasticsearch/tmpl/data-nodes.yml

For a raw YAML to JSON converter then something like https://www.npmjs.com/package/yaml-to-json should work great.

I am not really sure what YAML instead of JSON gives you, yes it is easier to read but getting the indentation right in a large template is a pain.

Generate the ARM using c#/Powershell/something else

I keep seeing this one come up and to be honest, and I have often thought about writing a transpiler so that I can write code in one language and have it output an ARM template. The general gist is that you create some code that knows how to create the JSON blob for a particular resource and you use that to generate the code. I like this approach if you can stop it from getting too complex, as you could create some classes in c# or something that represent different resources and then you could have a standard way to inherit the same settings across all of your projects. Want a VM, no problem inherit the ARMVM class and set the appropriate parameters.

If we knock up some interfaces in TypeScript (YAY TypeScript) then we can play around with creating templates dynamically that can be unit tested etc:

`interface parameter{ name : string; defaultValue : any | null; type : string; }

interface variable{
    name : string;
    value : any;
    type : string;
}

interface resource{
    name : string;
    type : string;
    apiVersion : string;
    location : string;
    properties : any;
    dependsOn : string[];
}

interface template{
    $schema : string;
    contentVersion : string;
    location : string;
    parameters : parameter[];
    variables : variable[];
    resources : resource[];
}

function generate(template : template) : string {
    return JSON.stringify(template, null, 2);
}

function getTemplate(location : string) :  template {
    
    let template : template = {
        $schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
        contentVersion: "1.0.0.0",
        location: location,
        parameters : [],
        variables : [],
        resources : []
    }
    return template;
}

function addParameter(template : template, parameter : parameter) : template {
    template.parameters.push(parameter);
    return template;
}

function addResource(template : template, resource : resource) : template{
    template.resources.push(resource);
    return template;
}

interface osProfile {
    computerName : string;
    adminUserName : string;
    adminPassword : string;
}
interface hardwareProfile {
    vmSize : string;
}
interface vmProperties{
    hardwareProfile : hardwareProfile;
    osProfile : osProfile;
}

interface virtualMachine extends resource {
    properties : vmProperties;
}

function virtualMachine(name : string, size : string) : resource {
        
    let vm : virtualMachine = {
        name: name,
        apiVersion: "2016-04-30-preview",
        type: "Microsoft.Compute/virtualMachines",
        location: 'North Europe',
        dependsOn: [],
        properties: {
            hardwareProfile: {
                vmSize: size
            },
            osProfile: {
                computerName: name,
                adminUserName: 'admin',
                adminPassword: 'da password'
            }
        }
    }

    return vm;
}

console.log(generate(
    addResource(    
    addResource(
            addParameter(
                    getTemplate('North Europe'), {name: 'vmName', defaultValue: "[concat('vm', 'Name')]", type: 'string'}), 
        virtualMachine('aVm', 'Standard_A2')),
        virtualMachine('aVm', 'Standard_A2'))        
    )
);`

It shows that you can create simple templates pretty easily, more complicated ones? Well the complexity increases but is it better than writing ARM templates as blobs of JSON?

beautiful generated arm templates

Would I use this approach? Maybe, I find it interesting, and I find the thought of writing a DSL even more interesting but not sure I would really be bothered to invest the time it would take to do this properly.

Another approach is to use a generator to create the template for you, and this yeoman generator is the sort of thing but a bit more manual:

https://brianfarnhill.com/2017/11/20/yo-arm-template-generating-arm-templates-yeoman/

The list of resources it can create is quite small but could be extended pretty easily. Given that yeoman templates are basically a big JSON blob, it shouldn’t be too hard to automate the generation of the yeoman generator to be used by developers to generate arm templates (keep up with that my little generator?)

3rd party tools, Terraform is the big daddy / Sparkle Formation

Terraform and sparkle formation were both written for AWS and added Azure support afterwards, in Azure’s case Microsoft is helping terraform to implement support for Azure. They are a way to write code that creates infrastructure (Infrastructure as code if that is what floats your boat). Terraform uses its own language, and Sparkle Formation uses Ruby, both are pretty straightforward.

These tools are both solved on the premise that they are cross-cloud compatible so you can deploy to AZW, Azure or GCP (GCP is the underrated king of the clouds btw, if you want reliable performance the CPU’s are 100% consistent, with AWS and Azure the only constant is the inconsistency in performance :) ). What this means is that you use the same tooling but the actual code can’t be pointed at a different cloud. For example, to deploy a virtual machine on Azure you need a resource "azurerm_virtual_machine" but to deploy a virtual machine to AWS, you use a resource "aws_instance"

I like terraform and would consider it for a project, the ability to create a plan to show what the deployment would do is great, a killer feature really. If it wasn’t for ARM templates and the ability to describe what you want and have that deployed, I would 100% use terraform but at the moment i’d stick with JSON blobs in ARM templates and perhaps use something else to keep them nice and tidy.

What else?

If you do something else then I would love to hear what it is you do, use the comments below, reach out to me at http://twitter.com/eddebug or by “the emails” ed@agilesql __ co __ uk