ARM logo

Guru Level Azure Usage with ARM

I have been wanting to write this article for a long time, since we frequently offer consultation for our customers and partners on the same subject. From the perspective of Azure, we are quite a special software house. Many of our team members are ex-Microsoft employees and we were working closely with Azure for years before the story of AJR Solutions began. Very often Microsoft customers had problems that could not be solved directly with Azure, but regardless there was a strong commitment to start using the service. In such cases we needed to dive deep into Azure, without the helpful tips of Stack Overflow gurus.

To get started with the intro, it’s best to understand a little of Azure’s history. Azure is already over 10 years old and its history can be splitter into two major parts. The first and longer part can be called Classic/ASM era and the newer part ARM (Azure Resource Manager) era. For a long time these two versions co-existed and only recently the Classic has been almost fully forgotten. Old Classic was a solidly working environment, but after Azure really started to grow, the variety of different interfaces and tools expanded too much. Everyone knew that the growth would only accelerate even more and the system needed some serious refactoring. This resulted in the development of ARM which had a common API interface for all services. The whole portal as well as PowerShell and other tools were now using the same API. For partners and customers the biggest benefit of this development was that Microsoft provided everybody with the same API. Basically, everyone can now replicate the Azure Portal with their own implementation.

So, what is it that we can do with ARM API? One could say that only sky is the limit, but practically speaking we can save money, improve the ease of maintenance and implement things that couldn’t be done with Portal or scripts directly. To achieve cost savings, we can for instance automate resource addition, cancellation and deletion. Once a partner wanted to automatically create a new virtual machine for each of their customers that would execute a simple job and then be destroyed. Nowadays Docker could often be used for these kinds of projects. However, in this case the partner was using Apache jClouds and Scala application and they also wanted to utilize custom Linux Image. In addition to the challenge, Apache jClouds was not supported by Azure ARM. Therefore, the whole project started with first implementing this support. This is something you cannot do with Portal and PowerShell.

If this started to sound interesting, you should continue reading as I will now demonstrate how Azure ARM API can be used. Of course this information is also available in the Microsoft documentation, but we want to keep it short and simple. Here’s the first steps you need to follow:

  • Register a new app to Azure AD. This is called service principal. Copy the app ID
  • Create a key for the new app. This key will be called client secret
  • Grant access for the app to use your Azure Subscription
  • Find out the directory ID of Azure Active Directory
  • Find out your Azure subscription ID

I will now briefly and with the help of pictures demonstrate how to get these IDs and secrets from the Azure portal. After that you will have the app ID, client secret, directory ID and subscription ID. This is all you need to be able to authenticate with Azure AD and to get a token that will enable you to use ARM API.

Let’s start with app registration. Open Azure Portal and select Azure Active Directory from the left sidebar.

Now select App Registrations from the newly opened menu and New App Registration from the tool bar. Give the app a name and select http://localhost as the sign-on url. App type can be set to default.

You will now see the Application ID that you should then copy.

Next select Settings from the tool bar and from there select Keys. Name the key and click Save. Copy the key.

Now you can return to Azure Active Directory menu and select Properties to find the Directory ID that needs to be copied.

Finally select All Resources from the left sidebar and from there select Subscriptions. Copy the subscription ID of the selected subscription.

Open the subscription and select IAM and New Role Assignment, in which you can search for the application and grant it Owner access.

Now we can get into business! First, we will make API calls to get the token and then call the ARM API with the token. You can use Postman, Curl or node.js which is the one I decided to use in this blog post.

The first step is to download Node.js and install Axios library that we use to connect to Azure:

[sourcecode language="plain"]
npm install axios
[/sourcecode]

When Node.js and Axios are installed, continue by writing the following code. Here you should of course use your own credentials and IDs.

[sourcecode language="plain"]
const Axios = require("axios")

const directoryID = "4a26b9c4-snip-e513d"
const client_secret = "LzQmDEx-snip-R49A4Nz3z0="
const app_id = "62168514-snip-51a21d41ddd2"
const sub_id = "77d1859b-snip-524fc247166f"
[/sourcecode]

Then implement the getToken()-function, which makes HTTP Post get the token. Print the token. Note that I am not implementing any error checks here to keep the code simple. Add getToken function:

[sourcecode language="plain"]
async function getToken() {
  const url = "https://login.microsoftonline.com/" + directoryID + "/oauth2/token?api-version=1.0"
  const body = "grant_type=client_credentials&resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=" + app_id + "&client_secret=" + client_secret
  const contentType = "application/x-www-form-urlencoded"
  const headers = [{'Content-Type': contentType}]
  const response = await Axios.post(url, body, headers)
  return response.data.access_token
}

async function main() {
  var token = await getToken()
  console.log(token)
}

main()
[/sourcecode]

Save code as code.js and try to run it:

[sourcecode language="plain"]
node code.js
[/sourcecode]

If the token was printed continue by trying HTTP GET method with the resource groups to receive a JSON list of the resource groups.

[sourcecode language="plain"]

async function getResourceGroups(token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups?api-version=2016-09-01"
  const headers = {'Authorization': "Bearer " + token}
  const response = await Axios.get(url, {headers})
  return response.data.value
}

async function main() {
  var token = await getToken()
  var groups = await getResourceGroups(token)
  console.log(groups)
}

main()

[/sourcecode]

Now that you are able to get the token and list groups, try to also add and remove groups:

[sourcecode language="plain"]
async function addResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const data = {location: "West US"}
  const response = await Axios.put(url, JSON.stringify(data), {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function deleteResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const response = await Axios.delete(url, {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}
[/sourcecode]

Note that running the delete function takes some time and you are not able to list a newly added group immediately. Delete operations can be polled, but that is a matter of another blog post. The following code involves everything that is needed to remove, add and list groups. We can use these same principles with other resources such as virtual machines, template deployments etc. Please refer to the documentation mentioned above to check what kind of parameters and paths these other resources use. Generally all the functions are very similar.

[sourcecode language="plain"]
const Axios = require("axios")

const directoryID = "4a26b9c4-snip-e513d"
const client_secret = "LzQmDEx-snip-R49A4Nz3z0="
const app_id = "62168514-snip-51a21d41ddd2"
const sub_id = "77d1859b-snip-524fc247166f"

async function getToken() {
  const url = "https://login.microsoftonline.com/" + directoryID + "/oauth2/token?api-version=1.0"
  const body = "grant_type=client_credentials&resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=" + app_id + "&client_secret=" + client_secret
  const contentType = "application/x-www-form-urlencoded"
  const headers = [{'Content-Type': contentType}]
  const response = await Axios.post(url, body, headers)
  return response.data.access_token
}

async function getResourceGroups(token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups?api-version=2016-09-01"
  const headers = {'Authorization': "Bearer " + token}
  const response = await Axios.get(url, {headers})
  return response.data.value
}

async function addResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const data = {location: "West US"}
  const response = await Axios.put(url, JSON.stringify(data), {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function deleteResourceGroup(name, token) {
  const url = "https://management.azure.com/subscriptions/" + sub_id + "/resourceGroups/" + name + "?api-version=2016-09-01"
  const contentType = "application/json"
  const response = await Axios.delete(url, {headers: {'Authorization': "Bearer " + token, 'Content-Type': contentType}})
  return response
}

async function main() {
  var token = await getToken()
  var resp = await addResourceGroup("apitesti", token)
  var groups = await getResourceGroups(token)
  console.log(groups)
}

main()

[/sourcecode]

When you run the code you should see something like this presented below. The code does obviously depend on your resource groups.

[sourcecode language="plain"]
[ { id:
     '/subscriptions/77d1859b-snip-524fc247166f/resourceGroups/ajr_dns',
    name: 'ajr_dns',
    location: 'northeurope',
    properties: { provisioningState: 'Succeeded' } },

  { id:
     '/subscriptions/77d1859b-snip-524fc247166f/resourceGroups/wordpress',
    name: 'wordpress',
    location: 'northeurope',
    properties: { provisioningState: 'Succeeded' } } ]
[/sourcecode]

Here it is! If you want to learn more, ask us for an Azure training. Our course offers you insight into many advanced Azure technologies in addition to ARM REST APIs. However, if you are too busy to learn all this by yourself, we can help you build your solution. Contact us to chat about cooperation possibilities.

Other posts