Search code examples
azureazure-devopsazure-functionsazure-rm-templateazure-bicep

How can I combine dynamic settings and static settings in a Function App using Bicep?


I am trying to defines settings (static and dynamic) for a Function App using Bicep.

The following is the module that generates the Function App itself. This module contains a loop that creates a collection of dynamic settings (which does work):

param serverFarmId string
param availableHubs array

resource functionAppProdSlotResource 'Microsoft.Web/sites@2021-03-01' = {
  name:'function-app-name'
  location: 'Canada Central'
  kind: 'functionapp,linux'
  identity: {
     type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: serverFarmId
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'dotnet|3.1'
      appSettings: [for (availableHubId, hubIndex) in availableHubs: {
        'name': 'AvailableHubsConfiguration__Hub__${hubIndex}'
        'value': 'Endpoint=https://hub-${availableHubId}.service.signalr.net;AccessKey=${listKeys(resourceId('Microsoft.SignalRService/SignalR', 'hub-${availableHubId}'), providers('Microsoft.SignalRService', 'SignalR').apiVersions[0]).primaryKey};Version=1.0;'
      }]
    }
  }
}

However I also have this other module that defines static settings for that same function:

param functionIdentity object = {
  prodSlotName: ''
  stagingSlotName: ''
}
@secure()
param systemStorageAccountConnectionString string
param applicationInsightsInstrumentationKey string
param stsDiscoveryEndpoint string
param accountsManagerEndpoint string
param azureActiveDirectory object
param updateManagerClientIdKeyVaultUrl string
param updateManagerClientSecretKeyVaultUrl string

var functionExtensionVersion = '~4'
var functionWorkerRuntime = 'dotnet-isolated'

resource prodSlotAppSettingsResource 'Microsoft.Web/sites/config@2021-03-01' = {
  name: '${functionIdentity.prodSlotName}/appsettings'
  properties: {
    AzureWebJobsStorage: systemStorageAccountConnectionString
    FUNCTIONS_EXTENSION_VERSION: functionExtensionVersion
    FUNCTIONS_WORKER_RUNTIME: functionWorkerRuntime
    APPINSIGHTS_INSTRUMENTATIONKEY: applicationInsightsInstrumentationKey
    StsConfiguration__DiscoveryEndpoints__0: stsDiscoveryEndpoint
    AccountsManagerConfiguration__Endpoint: accountsManagerEndpoint
    AzureActiveDirectoryConfiguration__ClientId: '@Microsoft.KeyVault(SecretUri=${updateManagerClientIdKeyVaultUrl})'
    AzureActiveDirectoryConfiguration__ClientSecret: '@Microsoft.KeyVault(SecretUri=${updateManagerClientSecretKeyVaultUrl})' 
    AzureActiveDirectoryConfiguration__Scope: azureActiveDirectory.scope
    AzureActiveDirectoryConfiguration__TokenEndpoint: azureActiveDirectory.tokenEndpoint
    AzureActiveDirectoryConfiguration__TenantId: subscription().tenantId
    AzureActiveDirectoryConfiguration__SubscriptionId: subscription().subscriptionId  
  }
}

Problem

The problem is that the 2nd module overrides the dynamic settings set by the 1st module. Because of the loop in the 1st module, I can't find a way to either prevent the override by the 2nd module or somehow combine the two.

Question

How can I combine dynamic settings and static settings in a Function App using Bicep?


Solution

  • I struggled for a few weeks to find an actual solution. What I have found rely on the concat function.

    I dropped the 2nd module (which used to define a resource for the Function's settings) and only keep the 1st one (which had the Function resource itself along with the dynamic settings).

    I now call the 1st module with an array that I built:

    module functionAppModule 'modules/function-app.bicep' = {
      name: 'Function_App'
      dependsOn: [
        serverFarmModule
      ]
      params: {
        [...]
        availableHubs: [for availableHub in availableHubs: 'Endpoint=https://hub-${environment}-${availableHub}.service.signalr.net;AccessKey=${listKeys(resourceId('Microsoft.SignalRService/SignalR', 'hub-${environment}-${availableHub}'), providers('Microsoft.SignalRService', 'SignalR').apiVersions[0]).primaryKey};Version=1.0;']
      }
    }
    

    In the module, I build 2 distinct arrays: one with the static settings and another one with the dynamic settings:

    var staticSettings = [
      {
        name: 'FUNCTIONS_EXTENSION_VERSION'
        value: '~4'
      }
      {
        name: 'FUNCTIONS_WORKER_RUNTIME'
        value: 'dotnet-isolated'
      }
      {
        name: 'AzureWebJobsStorage'
        value: systemStorageAccountConnectionString
      }
      {
        name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
        value: applicationInsightsInstrumentationKey
      }
      [...]
    ]
    
    var dynamicSettings = [for (availableHub, hubIndex) in availableHubs: {
      
      name: 'AvailableHubsConfiguration__Hub__${hubIndex}'
      value: availableHub
    }]
    

    Then, using the concat function, I merge the arrays together:

    var functionSettings = concat(staticSettings, dynamicSettings)
    

    Finally, I can assign the merged functionSettings array to the Function App by looping over it and specifying the name & value:

    resource functionAppProdSlotResource 'Microsoft.Web/sites@2021-03-01' = {
      name: 'function-app-name'
      location: 'Canada Central'
      kind: 'functionapp,linux'
      identity: {
         type: 'SystemAssigned'
      }
      properties: {
        serverFarmId: serverFarmId
        httpsOnly: true
        siteConfig: {
          linuxFxVersion: 'dotnet|3.1'
          appSettings: [for setting in functionSettings: {
            name: setting.name
            value: setting.value
          }]
        }
      }
    }