Azure function app's signalr_extension is not populated to use in signalR upstreams when creating a function app by Bicep

Viewed 159

I created an Azure Function App by Bicep and tried to get the signalr_extension's value to use in the "upstream" configuration section of a serverless Azure SignalR Service. This is how I try to obtain this value in Bicep:

var signalRKey = listKeys(resourceId('Microsoft.Web/sites/host', funcAppName, 'default'), '2022-03-01').systemkeys.signalr_extension

This is how I configure the signalR service's upstream:

urlTemplate: 'https://${funcAppName}.azurewebsites.net/runtime/webhooks/signalr?code=${signalRKey}'

Running the bicep templates leads to the failure below:

Encountered an error (ServiceUnavailable) from host runtime.

When I remove the {signalRKey} from urlTemplate and replace it with a fictitious hard-coded value, the signalR is provisioned successfully.

The other thing that I noticed was that the singalr_extension key value was not populated after the function app was provisioned.

What am I missing in this exercise?

2 Answers

The signalr_extension is only created once you've deployed your function app with a SignalRTrigger function.
You can generate this key upfront if you're deploying the Function App and signalR service at the same time:

param functionAppName string

// Create the function app key for signalR
resource signalRKey 'Microsoft.Web/sites/host/systemkeys@2021-03-01' = {
  name: '${functionAppName}/default/signalr_extension'
  properties: {
    name: 'signalr_extension'
  }
}

The ARM API to generate function keys is just pointing to the function app API so it could take some time before it become available (see issue on github).

I managed to get this working consistently by deploying the systemkey and signalr using module.
Also for function app running on linux, the AzureWebJobsStorage setting is mandatory.

functionapp-systemkey.bicep module:

param functionAppName string
param keyName string

resource signalRKey 'Microsoft.Web/sites/host/systemkeys@2021-03-01' = {
  name: '${functionAppName}/default/${keyName}'
  properties: {
    name: keyName
  }
}

signalr.bicep module:

param location string = resourceGroup().location

param signalRName string
param functionAppName string

resource signalR 'Microsoft.SignalRService/signalR@2022-02-01' = {
  name: signalRName
  location: location
  sku: {
    name: 'Free_F1'
    tier: 'Free'
    capacity: 1
  }
  properties: {
    features: [
      {
        flag: 'ServiceMode'
        value: 'Serverless'
      }
      {
        flag: 'EnableConnectivityLogs'
        value: 'true'
      }
    ]
    cors: {
      allowedOrigins: [
        '*'
      ]
    }
    tls: {
      clientCertEnabled: false
    }
    upstream: {
      templates: [
        {
          hubPattern: '*'
          eventPattern: '*'
          categoryPattern: '*'
          auth: {
            type: 'None'
          }
          urlTemplate: 'https://${signalRName}.azurewebsites.net/runtime/webhooks/signalr?code=${listKeys(resourceId('Microsoft.Web/sites/host', functionAppName, 'default'), '2022-03-01').systemkeys.signalr_extension}'
        }
      ]
    }
  }
}

main.bicep:

param location string = resourceGroup().location

param storageName string
param appServicePlanName string
param functionAppName string
param signalRName string

resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = {
  name: storageName
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  properties: {
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: 'Y1'
    tier: 'Dynamic'
    size: 'Y1'
    family: 'Y'
    capacity: 0
  }
  kind: 'functionapp'
  properties: {
    perSiteScaling: false
    elasticScaleEnabled: false
    maximumElasticWorkerCount: 1
    isSpot: false
    reserved: true
    isXenon: false
    targetWorkerCount: 0
    targetWorkerSizeId: 0
    zoneRedundant: false
  }
}

resource functionApp 'Microsoft.Web/sites@2021-03-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp,linux'
  properties: {
    serverFarmId: appServicePlan.id
    clientAffinityEnabled: false
    clientCertEnabled: false
    httpsOnly: true
    siteConfig:{
      linuxFxVersion: 'DOTNET|6.0'
      use32BitWorkerProcess: true
      ftpsState: 'FtpsOnly'
      cors: {
        allowedOrigins: [
          'https://portal.azure.com'
        ]
        supportCredentials: false
      }
      minTlsVersion: '1.2'
      appSettings: [
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet'
        }  
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};AccountKey=${listKeys(storage.id, '2019-06-01').keys[0].value};EndpointSuffix=core.windows.net;'
        }        
      ]
    }    
  }
}

var signalrKeyName = 'signalr_extension'
module signalrKey 'modules/functionapp-systemkey.bicep' = {
  name: '${functionAppName}-systemkey-${signalrKeyName}'
  params: {    
    functionAppName: functionApp.name
    keyName: signalrKeyName
  }
}

module signalr 'modules/signalr.bicep' = {
  name: signalRName
  params: {
    location: location
    functionAppName: functionApp.name
    signalRName: signalRName
  }
  dependsOn:[
    signalrKey
  ]
}

This feature is not available and feasible in App Service Plans. The signalr_extension must be populated manually after deploying the function app and signalR.

Related