Run Azure Functions from your Quickstart ARM Templates

I was recently confronted with the following problem: how can I run code from inside an Azure ARM Quickstart Template? Well, if you don’t know ARM Templates, they are essentially a declarative description of objects as you want to have them in Azure, but they don’t allow to run code as such. For example, see this question from someone who wanted to send an email notification upon completion of an ARM template.

The immediate answer would be something like creating Logic Apps or Azure Functions from the ARM Template. The problem there is that the template will create those Apps and Functions, but not necessarily run them. You could create them using a scheduled trigger, but then they would run multiple times, and you might want to have them executed only once.

My esteemed colleagued Mike Chen (see him in action here) put me on the right track: ARM Templates do not have a construct to call web APIs, but they do reach out for HTTP URLs when you nest other templates inside. For example, consider this quickstart template:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "variables": {
        "TemplateUri": "https://erjositofunctions.azurewebsites.net/api/fakeARMtemplate"
    },
    "resources": [
        {
            "name": "myNestedTemplate",
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2016-06-01",
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "[variables('TemplateUri')]",
                    "contentVersion": "1.0.0.0"
                },
                "parameters":  {}
            }
        }
    ]
}

As you have probably realized, this template does nothing else than running another template that is nested inside, and that is reachable under the URL https://erjositofunctions.azurewebsites.net/api/fakeARMtemplate. If you call that link you will get JSON code for an empty ARM template that does nothing at all:

{
    '$schema': 'https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#',
    'contentVersion': '1.0.0.0',
    'parameters': {},
    'variables': {},
    'resources': []
}

So essentially, our initial ARM template calls a URL and downloads another ARM template that does not do anything. Exciting isn’t it?

Well, actually the downloaded ARM template does not do anything, but that does not mean that nothing happens. As you have probably figured out from the URL, when you are calling https://erjositofunctions.azurewebsites.net/api/fakeARMtemplate you are actually running an Azure Function. Here is the function, written in C#:

#r "System.Web"
#r "System.Runtime"
#r "System.Threading.Tasks"
#r "System.IO"
using System;
using System.Net;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.IO;

public static async Task Run(HttpRequestMessage req, TraceWriter log)
{
   log.Info("C# HTTP trigger function processed a request.");

   // Get request body
   dynamic data = await req.Content.ReadAsAsync();

   //Do whatever you want to do, for example call a webhook with JSON payload
   using (var client = new HttpClient()) 
   { 
       client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
       client.DefaultRequestHeaders.Add("User-Agent", "AzureFunctions");
       var webhookUri = "https://myWebHook.com";
       StringContent webhookBody = new StringContent ("{'text': 'hello World'}");
       HttpResponseMessage response = client.PostAsync(webhookUri,webhookBody).Result; 
       var responseString = response.Content.ReadAsStringAsync().Result;
   }

   //Send back an empty template as JSON code, as answer to the original GET
   var template = @"{'$schema': 'https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#', 'contentVersion': '1.0.0.0', 'parameters': {}, 'variables': {}, 'resources': []}";
   HttpResponseMessage myResponse = req.CreateResponse(HttpStatusCode.OK);
   myResponse.Content = new StringContent(template, System.Text.Encoding.UTF8, "application/json");
   return myResponse;
}

As you can see, other than returning the JSON string with an empty, but syntactically correct ARM template, the Azure Function is calling a Webhook. This could do anything, from triggering another Azure Function, or an Azure Logic App, or whatever your imagination can think of.

Now, the only thing you need to do is adding a nested template deployment pointing to your Azure Function in your existing ARM template (that hopefully does do something), and your Azure Function will run every time the ARM template is deployed.

Your might be wondering now how to pass parameters to the Azure Function. Well, the master template will try to GET the nested template, so you cannot use POST. So you need to pass the arguments in the URI. For example, if you wanted to pass the resource group name where the ARM template is being deployed to, you could use this definition of the URI:

"TemplateUri": "[concat ('https://erjositofunctions.azurewebsites.net/api/fakeARMtemplate', '?resourcegroupname=', resourceGroup().name)]"

And your Function (in this example in C#) would have to extract the parameter to a variable, for example like this:

    // Parse query parameter resourcegroupname
    string resourceGroupName = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "resourcegroupname", true) == 0)
        .Value;

Now you can use the resourceGroupName variable inside of your code, or you can pass it forward to your webhook.

Have fun with ARM and Azure Functions!

9 thoughts on “Run Azure Functions from your Quickstart ARM Templates

  1. Thats a good idea. Thanks for sharing. I am going to try it to see if it works for my needs

    Liked by 1 person

  2. Wanis Elabbar

    This is a VERY cool idea! I will add an endpoint to my Python Flask app and see if it works 😀

    Like

    1. Let me know how it goes! 🙂

      Like

      1. Wanis Elabbar

        It works 🙂 but it triggers immediately during template verification rather than wait for the dependson condition, so I get a resource not available error…. Will place it in another linked template and trigger it there so I can get the dependson condition to work as intended.

        Like

  3. Wanis Elabbar

    Same result, the template verification phase triggers the api before the VM is deployed so i will have to add some logic to the Python side to poll the deployment and proceed when state is complete… unless there is a way to enforce the dependson in the ARM template :/

    Like

    1. What are you trying to do from the Azure function?

      Like

      1. Wanis Elabbar

        Trigger a VM extension installation, which of course requires the VM to exist first. Want to separate it from the ARM template. So that if the (linked) extension deployment fails, it doesn’t mark the whole deployment as failed – we just get notified and trigger it separately. For now I managed to control it by sending over the deployment name along with the rest of params and then poll the deployment until complete.

        I can see a lot of potential with this method. Thanks again for your post 🙂

        Like

  4. […] use deployment script, as it requires a resource group. The other workaround I found, is to use a deployment that gets the template from a URL. But, there is a problem with this as well. In my case, the endpoint gets called three times, even […]

    Like

  5. […] use deployment script, as it requires a resource group. The other workaround I found, is to use a deployment that gets the template from a URL. But, there is a problem with this as well. In my case, the endpoint gets called three times, even […]

    Like

Leave a comment