Event Grid trigger for Azure Functions with PowerShell
devops event-grid azure-functions

August 22, 2018

I wanted to trigger Azure Function by an event sent to Event Grid, which is an easy and well documented task. It's getting more difficult when you don't use Azure Portal and want to set everything up using command line.

There are certain combinations of services which make automated deployment not as simple as JSON template. One of them is Bot Service with Azure Active Directory (post coming soon), but in this article I'm going to look into Event Grid + Azure Functions combo.

tl;dr

Currently, it's not possible to use only ARM and Azure CLI to create both Event Grid and event subscription with Azure Functions as webhook endpoint. To do this, you have to include several REST calls and get something called system key.

Final PowerShell script is here.

Event Grid

My Event Grid resource is part of a larger infrastructure, so I've used Azure Resource Manager (ARM) to deploy the topic itself:

{
    "type": "Microsoft.EventGrid/topics",
    "name": "[parameters('gridName')]",
    "apiVersion": "2018-01-01",
    "location": "northeurope",
    "properties":{},
    "dependsOn": []
}

This Event Grid topic will be used to trigger Azure Functions function, but the event subscriptions are not part of Event Grid ARM template. Some scripting will be necessary...

Custom event subscription

Azure CLI is able to create various types of event subscriptions. To list the explanation and helpful examples, just type:

az eventgrid event-subscription create -h

In my case I needed a webhook subscription which sends events of type TextReceived to Azure Functions endpoint.

It could be a regular HTTP endpoint, but I used the Event Grid extension which provides a Functions trigger and handles the whole setup for me.

az eventgrid event-subscription create -g MyGroup --name MySubscription --topic-name MyTopic --endpoint "https://mysite.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName=Starter&code=ccooddee==" --endpoint-type webhook --included-event-types TextReceived

So far so good. The complication comes with the endpoint URL:

https://mysite.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName=Starter&code=ccooddee==

This endpoint is kindly provided by the Functions Event Grid extension and it's very easy to copy/paste it from the Azure Portal. Easy and also impossible to do during automated deployment...

Endpoint URL

The only real issue with this URL is the code= part. This code is not the HTTP trigger authorization code and it's not the master key from Function App either. According to the documentation, it's called system key. How do we get it?

Currently, there's no easy way (such as CLI command) to get this key. I had to put together a PowerShell script which goes through the process of:

  1. getting Kudu credentials with Azure CLI,
  2. encoding these credentials with Base64,
  3. using these credentials to get master key with PowerShell REST call,
  4. using master key to get system key with PowerShell REST call as well,
  5. finally creating Event Grid subscription with Azure CLI.

[Update 19. 3. 2019] Thanks to Martin Šamonil, who discovered an alternative way of obtaining the system key, which is needed to construct the trigger URL. See second script below.

Final script

Attribution: I'm not a PowerShell guru, so I took inspiration from the following great articles:

$resourceGroupName = "MyGroup";
$functionAppName = "mysite";
$starterFuncName = "Starter"; # trigger function (entry point)
$eventGridSubscriptionName = "MySusbcription";
$eventGridTopicName = "MyTopic";
$includedEventTypes = "TextReceived"; # space delimited list of event types

# if not logged in
# az login
# az account set --subscription <sub name>

# if eventgrid extension not installed
# az extension add --name eventgrid

# use Azure CLI to get Kudu creds
Write-Host "Getting Kudu credentials..."
$dep = az webapp deployment list-publishing-profiles -n $functionAppName -g $resourceGroupName --query "[?publishMethod=='MSDeploy']" -o json | ConvertFrom-Json;
$username = $dep.userName;
$pass = $dep.userPWD;

# base 64 creds
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${username}:${pass}"));

# use creds to get master key
Write-Host "Getting master key..."
$masterResp = Invoke-RestMethod -Uri "https://$functionAppName.scm.azurewebsites.net/api/functions/admin/masterkey" -Headers @{"Authorization" = "Basic " + $encoded};

# use master key to get system key
Write-Host "Getting system key..."
$systemKeyResp = Invoke-RestMethod -Uri "https://$functionAppName.azurewebsites.net/admin/host/systemkeys/eventgridextensionconfig_extension?code=$($masterResp.masterKey)";

# construct Function URL with system key
$functionUrl = "https://$functionAppName.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName=$starterFuncName&code=$($systemKeyResp.value)"

# create Event Grid subscription with Function URL
Write-Host "Creating Event Grid subscription..."
Write-Host ("- for URL: {0}" -f $functionUrl)
az eventgrid event-subscription create -g $resourceGroupName --name $eventGridSubscriptionName --topic-name $eventGridTopicName --endpoint $functionUrl --endpoint-type webhook --included-event-types $includedEventTypes

[Update 19. 3. 2019] Alternative way of obtaining the system key with only the access token from Azure CLI:

$resourceGroupName = "MyGroup";
$functionAppName = "mysite"
$starterFuncName = "Starter"
$eventGridSubscriptionName = "MySusbcription";
$eventGridTopicName = "MyTopic";
$includedEventTypes = "TextReceived"; # space delimited list of event types

# if not logged in
# az login
# az account set --subscription <sub name>

# if eventgrid extension not installed
# az extension add --name eventgrid

# get Azure access token from CLI
Write-Host "Getting access token from CLI..."
$azAccessToken = az account get-access-token -o tsv --query accessToken

# get Functions access token from SCM (Kudu), by providing Azure access token
Write-Host "Getting Function access key..."
$azFuncAccessToken = Invoke-RestMethod -Uri "https://$functionAppName.scm.azurewebsites.net/api/functions/admin/token" -Headers @{"Authorization" = "Bearer " + $azAccessToken};

# finally, use Functions access token to get Function's system key
Write-Host "Getting system key..."
$azFuncSystemKey = Invoke-RestMethod -Uri "https://$functionAppName.azurewebsites.net/admin/host/systemkeys/eventgridextensionconfig_extension" -Headers @{"Authorization" = "Bearer " + $azFuncAccessToken};

$functionUrl = "https://$functionAppName.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName=$starterFuncName&code=$($azFuncSystemKey.value)";

# create Event Grid subscription with Function URL
Write-Host "Creating Event Grid subscription..."
Write-Host ("- for URL: {0}" -f $functionUrl)
az eventgrid event-subscription create -g $resourceGroupName --name $eventGridSubscriptionName --topic-name $eventGridTopicName --endpoint $functionUrl --endpoint-type webhook --included-event-types $includedEventTypes

Found something inaccurate or plain wrong? Was this content helpful to you? Let me know!

📧 codez@deedx.cz