azureazure-devopsazure-functionsazure-resource-managerazure-bicep

Creating a host key on newly created function app fails


I'm trying to create a new host key for a newly created function app and it fails with the following error:

{
    "status": "Failed",
    "error": {
        "code": "BadRequest",
        "message": "Encountered an error (InternalServerError) from host runtime.",
        "details": [
            {
                "message": "Encountered an error (InternalServerError) from host runtime."
            },
            {
                "code": "BadRequest"
            },
            {}
        ]
    }
}

Also, it seems to mess up the portal as well:

enter image description here

When doing the same when the function app already exists, it works though.

Here's what I'm doing

module myFunctionModule '../function-bundle.bicep' = {
  name: parentDeploymentName
  params: {
    storageAccountName: storageAccountName
    functionAppName: functionName
    functionAppPlanName: appPlanName
    keyVaultName: keyVaultName
    tags: functionAppTags
    location: location
    parentDeploymentName: parentDeploymentName
  }
}

resource waitDeployment 'Microsoft.Resources/deployments@2021-04-01' = {
  name: 'waitForFunctionApp'
  properties: {
    mode: 'Incremental'
    template: {
      '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
      contentVersion: '1.0.0.0'
      resources: []
    }
  }
  dependsOn: [
    myFunctionModule
  ]
}

resource myKey 'Microsoft.Web/sites/host/functionKeys@2022-03-01' = {  
  name: '${functionName}/default/myKey'  
  properties: {  
    name: 'myKey'  
  }
  dependsOn: [
    waitDeployment
  ]  
} 

I've tried with and without the waitDeployment, it fails regardless.

Here's the content of the function-bundle module

param tags object
param functionAppName string
param functionAppPlanName string
param storageAccountName string
param keyVaultName string
param location string
param parentDeploymentName string

module functionModule './function.bicep' = {
  name: take('${parentDeploymentName}-functionDeployment', 64)
  params: {
    location: location
    storageAccountName: storageAccountName
    appServicePlanName: functionAppPlanName
    functionAppTags: tags
    functionAppName: functionAppName

  }
}

module addKeyVaultAccessDeploymentModule './add-kv-access.bicep' = {
  name: take('${parentDeploymentName}-functionAddKeyVaultAccessDeployment', 64)
  params: {
    keyVaultName: keyVaultName
    principalId: functionModule.outputs.functionIdentity.principalId
    roleName: 'KeyVaultSecretsUserRole'
    principalType: 'ServicePrincipal'
  }
}

@secure()
output storageAccountKey string = functionModule.outputs.storageAccountKey
@secure()
output storageAccountConnectionString string = functionModule.outputs.storageAccountConnectionString
output functionUrl string = functionModule.outputs.functionUrl

And function.bicep

param location string
param storageAccountName string
param appServicePlanName string
param functionAppTags object
param functionAppName string
param sku object = {
    name: 'B1'
    tier: 'Basic'
    size: 'B1'
    family: 'B'
    capacity: 1
  }


resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: location
  tags: functionAppTags
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}

resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = {
  name: appServicePlanName
  location: location
  properties: {
    reserved: true
  }
  kind: 'functionapp,linux'
  sku: sku
  tags: functionAppTags
}

resource function 'Microsoft.Web/sites@2024-04-01' = {
  name: functionAppName
  location: location
  tags: functionAppTags
  kind: 'functionapp,linux'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    enabled: true
    serverFarmId: appServicePlan.id
    siteConfig: {
      numberOfWorkers: 1
      linuxFxVersion: 'DOTNET-ISOLATED|9.0'
      appSettings: [
      {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
      }
      {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'dotnet-isolated'
      }
      {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
       }
  ]
    }
  }
}

var listKeyResult = storageAccount.listKeys()

output functionIdentity object = function.identity
@secure()
output storageAccountKey string = listKeyResult.keys[0].value
@secure()
output storageAccountConnectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listKeyResult.keys[0].value};EndpointSuffix=core.windows.net'
output functionUrl string = 'https://${function.properties.defaultHostName}/api'
output defaultHostKey string = '' //listKeys(resourceId('Microsoft.Web/sites/host', functionAppName, 'default'), function.apiVersion).functionKeys.default

I've also tried

resource myKey 'Microsoft.Web/sites/host/functionKeys@2022-03-01' = {  
  name: '${functionName}/default/myKey'  
  properties: {  
    name: 'myKey'  
  }
  dependsOn: [
    myFunctionModule
  ]  
}  

Still fails.

Could it be that depending on a module isn't as reliable as depending on a resource ?


Solution

  • I've have been able to do it this way.

    Underneath the definition for myFunctionModule

    Since the actual resource is contained in the module and not exposable through its outputs

    resource existingFunction 'Microsoft.Web/sites@2024-04-01' existing = {
      name: functionName
      dependsOn: [myFunctionModule]
    }
    

    Then fetch the site host for the function

    resource siteHost 'Microsoft.Web/sites/host@2022-03-01' existing = {
      name: 'default'
      parent: existingFunction
    }
    

    Then create the key

    resource myKey 'Microsoft.Web/sites/host/functionKeys@2022-03-01' = {  
      name: 'myKey'  
      properties: {  
        name: 'myKey'  
      }
      parent: siteHost
    }
    

    Then I can use the result as output, if needed

    @secure()
    output myKey string = myKey.properties.value
    

    Both myKey and siteHost are flagged with a warning that:

    Resource type "Microsoft.Web/sites/host/functionKeys@2022-03-01" does not have types available. Bicep is unable to validate resource properties prior to deployment, but this will not block the resource from being deployed

    But it still works. You have to make sure that when creating the function app, those appSettings are present if you want to manage its host keys. (I used to update the app settings after creation, which was a problem)

    var initialAppSettings = [
      {
        name: 'AzureWebJobsStorage'
        value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=core.windows.net'
      }
      {
        name: 'FUNCTIONS_WORKER_RUNTIME'
        value: 'dotnet-isolated'
      }
      {
        name: 'FUNCTIONS_EXTENSION_VERSION'
        value: '~4'
      }
    ]
    

    I have not validated which one(s) are needed exactly, but if you ignore them altogether this will fail.