As it currently stands, the Azure Java SDK has not been updated in quite some time. As such, it does not expose the same set of functionality that the Azure SDK for .NET or the Azure SDK for Node.JS. Specifically, it does not yet provide support for Azure Resource Manager (ARM).

Lack of ARM support and a desire to use the new APIs and role based access control that ARM exposes was a problem for a partner I was recently working with, they were a Java/Scala shop. The three options for them ended up being:

  1. Wait for an updated Java SDK
  2. Use the Azure Xplat-CLI and shell out to execute operations
  3. Use the Azure Resource Manager REST APIs

Waiting doesn’t work when trying to move to Azure sooner than later. Shelling out and the complexity and instability that entails (parsing output, handling errors, etc), is an option for a few small things, but nothing complex. So, we decided to go with the Azure Resource Manager REST APIs until the updated SDK is available.

There are two components that are important when using the ARM REST APIs:

  1. Authenticating
  2. Making the actual REST call

The first is covered in a prior post Authenticating to Azure Resource Manager Using Java. There is an example of the second in that post as well, but for the partner I was working with wanted to work with ARM templates. MSDN has documentation for the REST APIs for Template Deployments.

Templates allow one to create something as simple as a Storage Account or a Security Group to setting up complete deployments, for instance a Docker Swarm Cluster. Many pre-made templates can be found on github under Azure Quickstart Templates. For this post, we will keep it simple by creating a simple Storage Account in an existing Resource Group. Resource Groups can be created using the REST APIs documented here.

The Anatomy of a Template Deployment

Template Deployments require a REST call to the deployment URL and the appropriate JSON content body. For creating a deployment, the REST call is defined as:

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

And the JSON for the simplest form for a Template Deployment is:

{
  "properties": {
    "template": {
      ... template body ...
    },
    "mode": "Incremental",
    "parameters": {
      ... parameters body ...
    }
  }
}

The two pieces that one typically fills in when making a request are the parameters and the template. If one is using the Azure CLI, one specifies the template JSON and possibly a parameters JSON.

Depending on the template, the parameters may have a specific schema as well. Looking at the schema on Github for how to Create a Standard Storage Account, there is a parameters section of the template with required fields and, if appropriate, allowed values.

The parameters block for the above template lists the following parameters:

"parameters": {
  "newStorageAccountName": {
    "type": "string",
    "metadata": {
      "description": "Name of the Storage Account"
    }
  },
  "storageAccountType": {
    "type": "string",
    "defaultValue": "Standard_LRS",
    "allowedValues": [
      "Standard_LRS",
      "Standard_GRS",
      "Standard_ZRS"
    ],
    "metadata": {
      "description": "Storage Account type"
    }
  },
  "location": {
    "type": "string",
    "allowedValues": [
      "East US",
      "West US",
      "West Europe",
      "East Asia",
      "Southeast Asia"
    ],
    "metadata": {
      "description": "Location of storage account"
    }
  }
},

To set up the JSON body for the Template Deployment for a Standard Storage Account, you would need to create the following JSON request body:

{
  "properties": {
    "template": {
      <content of the create storage account template>
    },
    "mode": "Incremental",
    "parameters": {
      "newStorageAccountName": {
        "value": "<account name>"
      },
      "storageAccountType": {
        "value": "<account type>"
      },
      "location": {
        "value": "<account location>"
      }
    }
  }
}

When creating the REST request, in addition to the values necessary for authenticating to the Azure service, the following values are needed:

  • subscription id: the GUID for the subscription you are publishing to
  • resource group name: what resource group is this deployment going to occur under
  • deployment name: what is the unique name for this deployment

The Real Thing, A Deployment Request

If one were to make a request to create a storage account with the following settings:

  • newStorageAccountName: testaccount2
  • storageAccountType: Standard_LRS
  • location: West US

And account settings as the following:

  • subscription id: d1188e07-7c04-41b6-8765-9d8fc3c5fe2a
  • resource group name: testresourcegroup
  • deployment name: sadeploy1

The HTTP request (on the wire) is going to resemble:

PUT /subscriptions/d1188e07-7c04-41b6-8765-9d8fc3c5fe2a/resourcegroups/testresourcegroup/deployments/sadeploy1?api-version=2014-04-01-preview HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 1017
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9iMzY4ZmFkOS0zOTU1LTRiYmMtOGYxNi0zNDA3NDNjZWZkZDcvIiwiaWF0IjoxNDM5NDE3ODA4LCJuYmYiOjE0Mzk0MTc4MDgsImV4cCI6MTQzOTQyMTcwOCwidmVyIjoiMS4wIiwidGlkIjoiYjM2OGZhZDktMzk1NS00YmJjLThmMTYtMzQwNzQzY2VmZGQ3Iiwib2lkIjoiOTAwZTE5ZTUtZmVlNy00NjkzLThkZGUtMDdjOGY3YTRjMGM3IiwidXBuIjoiY2xpYWRtaW5Ac3BpZWxtaXRjbG91ZG91dGxvb2sub25taWNyb3NvZnQuY29tIiwicHVpZCI6IjEwMDMwMDAwOTMwOTAxNzQiLCJzdWIiOiJKR3hpbmxCVzJTTnJoaXZ3WDUwVFRlLUZDVklZRmx5T2JLSFlxWFZ0NUV3IiwiZ2l2ZW5fbmFtZSI6IkNsaSIsImZhbWlseV9uYW1lIjoiQWRtaW4iLCJuYW1lIjoiY2xpYWRtaW4iLCJhbXIiOlsicHdkIl0sImdyb3VwcyI6WyI0YmViZmQ5Mi0yMTljLTQ4NjktYmEwOS05MjlhMjMwNDkxODUiXSwidW5pcXVlX25hbWUiOiJjbGlhZG1pbkBzcGllbG1pdGNsb3Vkb3V0bG9vay5vbm1pY3Jvc29mdC5jb20iLCJ3aWRzIjpbImYwMjNmZDgxLWE2MzctNGI1Ni05NWZkLTc5MWFjMDIyNjAzMyJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsImFjciI6IjEifQ.rG_CA0ACq0HYZCP7PVZrWBFoFJqmPARtvSfGR04BUXvL03OB3GH6yCC2N6OehDvSdMDgJv2r5rX7SeCQxJvg2oy-FyTNo0PtjoMDnbOnrmH7UTSNQVh-KI3kCJTMi1-qoxTh7kJ9W118bTma6N9e4RRp19xTsRy2UL5fwvhFZ6G1VkEdjHryBwj22_WU0mcxS_xMx2TIBgrL294J8Sp_QmG8bZy-iC8dUvkp7WgMvE8OH-T0rheYUk-BKofguO5V2BV4Hf99cGuX5Sj4O5eguPWpOEsXpaX1ZDVfMXY-LOOXVsewvdTtqBU3gD5yFjH9YNDCfGQAH5SEE-vazxXWNQ
host: management.azure.com
Connection: close

{"properties":{"template":{"$schema":"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#","contentVersion":"1.0.0.0","parameters":{"newStorageAccountName":{"type":"string","metadata":{"description":"Name of the Storage Account"}},"storageAccountType":{"type":"string","defaultValue":"Standard_LRS","allowedValues":["Standard_LRS","Standard_GRS","Standard_ZRS"],"metadata":{"description":"Storage Account type"}},"location":{"type":"string","allowedValues":["East US","West US","West Europe","East Asia","Southeast Asia"],"metadata":{"description":"Location of storage account"}}},"resources":[{"type":"Microsoft.Storage/storageAccounts","name":"[parameters('newStorageAccountName')]","apiVersion":"2015-05-01-preview","location":"[parameters('location')]","properties":{"accountType":"[parameters('storageAccountType')]"}}]},"parameters":{"newStorageAccountName":{"value":"testaccount2"},"storageAccountType":{"value":"Standard_LRS"},"location":{"value":"West US"}},"mode":"Incremental"}}

Expanding the body of the call and formatting it a bit better, you see:

{
    "properties": {
        "template": {
            "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
            "contentVersion": "1.0.0.0",
            "parameters": {
                "newStorageAccountName": {
                    "type": "string",
                    "metadata": {
                        "description": "Name of the Storage Account"
                    }
                },
                "storageAccountType": {
                    "type": "string",
                    "defaultValue": "Standard_LRS",
                    "allowedValues": ["Standard_LRS", "Standard_GRS", "Standard_ZRS"],
                    "metadata": {
                        "description": "Storage Account type"
                    }
                },
                "location": {
                    "type": "string",
                    "allowedValues": ["East US", "West US", "West Europe", "East Asia", "Southeast Asia"],
                    "metadata": {
                        "description": "Location of storage account"
                    }
                }
            },
            "resources": [{
                "type": "Microsoft.Storage/storageAccounts",
                "name": "[parameters('newStorageAccountName')]",
                "apiVersion": "2015-05-01-preview",
                "location": "[parameters('location')]",
                "properties": {
                    "accountType": "[parameters('storageAccountType')]"
                }
            }]
        },
        "parameters": {
            "newStorageAccountName": {
                "value": "testaccount2"
            },
            "storageAccountType": {
                "value": "Standard_LRS"
            },
            "location": {
                "value": "West US"
            }
        },
        "mode": "Incremental"
    }
}

Which conforms to what was outlined above.

So, Uh, Where’s the Java?

To perform the above in Java, recall the two requirements:

  1. Authenticate to Azure with permissions on https://management.azure.com
  2. Perform the REST call

The code example for how to authenticate was previously covered in this post. Rather than covering that part again, there really is nothing magical about making HTTP calls in Java.

Using Apache HttpClient, the code (assuming you had a string already for the Template), would resemble:

String url = "/subscriptions/d1188e07-7c04-41b6-8765-9d8fc3c5fe2a/resourcegroups/testresourcegroup/deployments/sadeploy1?api-version=2014-04-01-preview";
String authToken = authResult.getAccessToken();
String templateBody = loadTemplate(...);

boolean success = false;
try {
    final HttpClient httpClient = new DefaultHttpClient();
    HttpConnectionParams
            .setConnectionTimeout(httpClient.getParams(), 10000);
    HttpPut httpOp = HttpPut(url);
    httpOp.addHeader("Accept", "application/json");
    httpOp.addHeader("Content-Type", "application/json");
    httpOp.addHeader("Authorization", "Bearer " + authToken);
    StringEntity entity = new StringEntity(body, "UTF-8");
    entity.setContentType("application/json");
    httpOp.setEntity(entity);
    HttpResponse response = httpClient.execute(httpOp);
    int statusCode = response.getStatusLine().getStatusCode();
    success = (statusCode / 100) == 2 ? true : false;
} catch(IOException ex) {
    System.out.println("Deploy/validate failed.");
    ex.printStackTrace();
}

return success;

This example is really not that difficult. The only trick to any of this is understanding the APIs and what data they require and how to make the calls.

Example code implementing the above, plus some additional Template Deployment management functions can be found on Github under azure-rest-examples. Specifically, ARMTemplateExample. The code there implements “deploy”, “validate”, and “status”.