7

I am trying to automate the deployment of our environment vir ARM templates. I can deploy Event Grid and Function Apps but now I need to subscribe the function app to the Event Grid after the function app is deployed. Is there a way to get the webhook url for the function app

  1. Via a ARM
  2. Some other component (Powershell) in the Release pipeline

We are able to create the subscription via ARM once we have the webhook url - but to get to the correct url seems to be where we are falling of the boat.

Any help please

Van
  • 600
  • 4
  • 12
  • can you use the REST APIs? have a look at the following link: https://stackoverflow.com/questions/50367435/is-there-a-option-to-get-the-event-grid-trigger-url-key-at-output-value-from-t?noredirect=1&lq=1 – Roman Kiss Jul 26 '18 at 11:58
  • Yes, thanks. Have seen that post - How to do the rest call via VSTS is where I am stuck now. Thanks – Van Jul 26 '18 at 12:10

5 Answers5

4

I managed to get this working with the help of the answers from @Van and @Barrie above.

This script returns the masterkey and defaultkey from the azure api, which enables you to create an eventgrid subscription from a functionApp/webApp in your release pipeline.

Van's script (30 Jul) worked with FA version 1 but it did not work for FunctionApps V2 (something was changed in the api). When using this script in V2 the error was:

Runtime keys are stored on blob storage. This API doesn't support this configuration. Please change Environment variable AzureWebJobsSecretStorageType value to 'Files'.

I amended this script and now it works with V2:

#DEBUG: when debugging (running in powershell on local pc) you need to comment out the next line by starting the line with #
param($resourceGroupName, $webAppname)

function Get-PublishingProfileCredentials($resourceGroupName, $webAppName){
        $resourceType = "Microsoft.Web/sites/config"
        $resourceName = "$webAppName/publishingcredentials"
        $publishingCredentials = Invoke-AzureRmResourceAction -ResourceGroupName $resourceGroupName -ResourceType $resourceType -ResourceName $resourceName -Action list -ApiVersion 2015-08-01 -Force
        return $publishingCredentials
}

function Get-KuduApiAuthorisationHeaderValue($resourceGroupName, $webAppName){
        $publishingCredentials = Get-PublishingProfileCredentials $resourceGroupName $webAppName
        return ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword))))
}

function Get-MasterAPIKey($kuduApiAuthorisationToken, $webAppName ){    
        $bearerToken = Invoke-RestMethod -Uri https://$webAppName.scm.azurewebsites.net/api/functions/admin/token  -Headers @{"Authorization"=$kuduApiAuthorisationToken;"If-Match"="*"} 

        $masterkeyResponse = Invoke-RestMethod -Method GET -Headers @{Authorization=("Bearer {0}" -f $bearerToken)} -Uri "https://$webAppName.azurewebsites.net/admin/host/systemkeys/_master" 
        $masterKeyValue = $masterkeyResponse.value
        return $masterKeyValue
}

function Get-HostAPIKeys($kuduApiAuthorisationToken, $webAppName, $masterKey ){
        $apiUrl = "https://$webAppName.azurewebsites.net/admin/host/keys?code=$masterKey"
        $result = Invoke-WebRequest $apiUrl
        return $result
}

#DEBUG: when debugging this in powershell on my local pc I use this to authenticate (remove # to uncomment the next line):
#Login-AzureRmAccount -SubscriptionName "Insert_Subscription_Name_Here"


#DEBUG: when debugging you need to set these parameters:
# $resourceGroupName = "Insert_ResourceGroup_Name_Here"
# $webAppname = "Insert_FunctionApp_Name_Here"


#Auth Header
$kuduToken = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $webAppName

#MasterKey
$masterKey = Get-MasterAPIKey $kuduToken $webAppName
Write-Host "masterKey = " $masterKey

#Default Key
$result = Get-HostAPIKeys $kuduToken $webAppName $masterkey
$keysCode =  $result.Content | ConvertFrom-Json
Write-Host "default Key = " $keysCode.Keys[0].Value

#Set Return Values:
$faMasterKey = $masterkey
$faDefaultKey = $keysCode.Keys[0].Value

Write-Output ("##vso[task.setvariable variable=fa_MasterKey;]$faMasterKey")
Write-Output ("##vso[task.setvariable variable=fa_DefaultKey;]$faDefaultKey")

There is only a small difference between this script and Van's script. The major difference is that this script will work on Azure CLI Functions V2. More info: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid

Spyder
  • 3,784
  • 3
  • 26
  • 15
  • For an eventgrid function need to update the function so it requests the master key from "https://$webAppName.azurewebsites.net/admin/host/systemkeys/eventgrid_extension" – Nathan Hart Oct 25 '18 at 16:59
  • @NathanHart, that would retrieve the systemkey, rather than the masterkey, not? – Jacques Bosch Dec 15 '18 at 15:49
2

You should be able to output the webhook URL like this:

"outputs": {       
    "Url": {
        "type": "string",
        "value": "[listsecrets(resourceId('Microsoft.Web/sites/functions', parameters('yourFunctionAppName'), parameters('yourFunctionName')),'2015-08-01').trigger_url]"
    }        
}

Here is a related answer.

Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
  • 1
    No, this is not it. The trigger_url I can find but it does not work as a EventGrid webhook subscription. The URL that we are looking for is: https://{functionApp}.azurewebsites.net/runtime/webhooks/EventGridExtensionConfig?functionName={function}&code={masterKey}. Where the masterKey is what we are stugling with - I have come accross other post of how to call a rest api to get the key. But this too is not so easy to accomplish in VSTS – Van Jul 26 '18 at 12:09
  • Okay, but you should be able to get the key if you change `.trigger_url` to `.key` and populate the URL yourself. Then within VSTS you can use the arm-output extension to retrieve the key and work with it: https://marketplace.visualstudio.com/items?itemName=keesschollaart.arm-outputs – Martin Brandl Jul 26 '18 at 12:56
  • Unfortunately this key does not work for a EventGrid subscription - I just double checked this and it does not work. To make it worse - if you look in the portal at the function keys (master or default) it is not the same key as what is given in the 'listsecrets' command. The keys seen in the portal does actually work. I am so frustrated. I have wasted so many hours on this - The documentation on this is not good enough. – Van Jul 27 '18 at 07:18
  • I don't think that's possible currently. Please open an issue here https://github.com/Azure/azure-functions-host/issues – ahmelsayed Jul 27 '18 at 19:12
1

I finally managed to get this working. In the end I created a powershell task that extracted the masterkey (and defaultKey) and now I am able to create my eventgrid subscriptions.

Thanks to

Here is the powershell script I use:

param($resourceGroupName, $webAppname)

function Get-PublishingProfileCredentials($resourceGroupName, $webAppName){

$resourceType = "Microsoft.Web/sites/config"
$resourceName = "$webAppName/publishingcredentials"
$publishingCredentials = Invoke-AzureRmResourceAction -ResourceGroupName 
$resourceGroupName -ResourceType $resourceType -ResourceName $resourceName -Action 
list -ApiVersion 2015-08-01 -Force
return $publishingCredentials   
}

function Get-KuduApiAuthorisationHeaderValue($resourceGroupName, $webAppName){

$publishingCredentials = Get-PublishingProfileCredentials $resourceGroupName $webAppName
return ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword))))
}

function Get-MasterAPIKey($kuduApiAuthorisationToken, $webAppName ){

$apiUrl = "https://$webAppName.scm.azurewebsites.net/api/functions/admin/masterkey"

$result = Invoke-RestMethod -Uri $apiUrl -Headers @{"Authorization"=$kuduApiAuthorisationToken;"If-Match"="*"} 

return $result`
}

function Get-HostAPIKeys($kuduApiAuthorisationToken, $webAppName, $masterKey ){

$apiUrl = "https://$webAppName.azurewebsites.net/admin/host/keys?code=$masterKey"

$result = Invoke-WebRequest $apiUrl

return $result`
}

$accessToken = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $webAppname

$adminCode = Get-MasterAPIKey $accessToken $webAppname

Write-Host "masterKey = " $adminCode.Masterkey

$result = Get-HostAPIKeys $accessToken $webAppname $adminCode.Masterkey

$keysCode =  $result.Content | ConvertFrom-Json

Write-Host "default Key = " $keysCode.Keys[0].Value

$faMasterKey = $adminCode.Masterkey
$faDefaultKey = $keysCode.Keys[0].Value

Write-Output ("##vso[task.setvariable variable=fa_MasterKey;]$faMasterKey")
Write-Output ("##vso[task.setvariable variable=fa_DefaultKey;]$faDefaultKey")

This will output the:

  • masterkey in the 'fa_MasterKey' variable
  • defaultKey in the 'fa_DefaultKey' variable

(I am going to attempt to create a VSTS task and publsih it to the marketplace - details will follow)

Van
  • 600
  • 4
  • 12
  • This worked for us up until MS released FA version ~2. Function Apps now store keys in blob - This results in Kudu not being able to reach the keys, as it is no longer stored in Kudu. According to our corresponding wit MS there is a temporary work around to switch the key storage Files - But it is only temporary. No news on permanent solution OR stay on version ~1 – Van Oct 01 '18 at 09:56
1

I was in the same boat as you and eventually got this working but it took quite a bit of time to work out the correct endpoints etc. What I was trying to do is create an event subscription for one of my resource groups using az eventgrid event-subscription create. The main issue was with the --endpoint argument as that has a code query string parameter on it. I could find this in the Azure portal quite easily by doing this:

  1. Go to my function app
  2. Go to the function I want to add as handler for the event subscription
  3. Click "Add Event Grid subscription"
  4. Copy the "Subscriber Endpoint" value

However, I wanted to do this all programmatically which proved to be difficult. In the end, the bash script I used looks like this:

#!/bin/bash

appName="myfunctionappname"
resourceGroup="myresourcegroupname"

# First do a KUDU login so we can get a JWT bearer token
user=$(az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup --query "[?publishMethod=='MSDeploy'].userName" -o tsv)
pass=$(az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup --query "[?publishMethod=='MSDeploy'].userPWD" -o tsv)
bearerToken=$(curl -s -u $user:$pass https://$appName.scm.azurewebsites.net/api/functions/admin/token | tr -d '"')

# Creating event grid subscription linked against the endpoint is an admin function so requires a master key
masterKeyResponse=$(curl -s -H "Authorization: Bearer $bearerToken" "https://$appName.azurewebsites.net/admin/host/systemkeys/_master")
masterKey=$(echo $masterKeyResponse | jq '.value' | tr -d '"')

functionName="MyFunctionName"
az eventgrid event-subscription create -g $resourceGroup --name "test-event-subscription" --endpoint "https://$appName.azurewebsites.net/runtime/webhooks/EventGridExtensionConfig?functionName=$functionName&code=$masterKey"
Barrie
  • 1,444
  • 19
  • 26
0

For V 2.0 and 3.0 Function Apps you must set AzureWebJobsSecretStorageType to files:

  "properties": {
    "name": "[variables('functionsName')]",
    "siteConfig": {
      "appSettings": [
        {
          "name": "FUNCTIONS_EXTENSION_VERSION",
          "value": "~3"
        },
        {
          "name": "AzureWebJobsSecretStorageType",
          "value": "files"
        },

Then you can get the url or the key and url using:

  "outputs": {
    "mValidateConfigurationUrl": {
      "type": "string",
      "value": "[listsecrets(resourceId('Microsoft.Web/sites/functions', variables('functionsName'), 'mValidateConfiguration'),'2015-08-01').trigger_url]"
    },
    "mValidateConfigurationUrlObj": {
      "type": "object",
      "value": "[listsecrets(resourceId('Microsoft.Web/sites/functions', variables('functionsName'), 'mValidateConfiguration'),'2015-08-01')]"
    }
jlo-gmail
  • 4,453
  • 3
  • 37
  • 64