Search code examples
azureazure-bicep

Bicep Referencing a storage account when setting appSettings in new app service, errors out with StorageAccountIsNotProvisioned


Using bicep, I am creating a storage account, and creating an app service. For the app service, it is attempting to to set the AzureWebJobsStorage app setting to point to the storage account. I get the following error:

...
"details": [
  {
    "code": "StorageAccountIsNotProvisioned",
    "message": "The storage account provisioning state must be 'Succeeded' before executing the operation."
  }
]
...

It seems that even though I have dependencies set up, the creation of the function app does not wait for successful completion of the storage account, and there must be validation on the function app appsettings values. I think the error is referring to the AzureWebJobsStorage appsettings key of the appService resource in the bicep below.

If I run the bicep a second time, everything works, because the storage account has already been created. is there some way in the bicep file to ensure that the storage account has been successfully created before creating the function app?

What's interesting is it never errors out on the reference to the application insights, only on the reference to the storage account, and both references are within appsettings.

In one module I create my storage account:

param tags object
param storeAccountName string
param location string = resourceGroup().location
param skuName string = 'Standard_LRS' 


resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: toLower(storeAccountName)
  kind: 'StorageV2'
  location : location
  sku: {
    name: skuName
  }
  tags: tags
}

In another module I create a function app, and refer to the storage account:

param tags object
param name string
param location string = resourceGroup().location
param appServicePlanId string
param applicationInsightsName string
param storageAccountName string

resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = {
    name: applicationInsightsName
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
    name: toLower(storageAccountName)
}

resource appService 'Microsoft.Web/sites@2022-03-01' = {
    name: name
    location: location
    kind: 'functionapp'
    properties: {
        serverFarmId: appServicePlanId
        siteConfig: {
            appSettings: [
                //Storage settings
                {
                    name: 'AzureWebJobsStorage'    
                    value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
                  }
                //Application insight settings
                {
                    //https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-from-instrumentation-keys-to-connection-strings
                    name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
                    value: appInsights.properties.ConnectionString
                }
                {
                    name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
                    value: appInsights.properties.InstrumentationKey
                }

                {
                    name: 'APPINSIGHTS_PROFILERFEATURE_VERSION'
                    value: '1.0.0'
                }
                {
                    name: 'APPINSIGHTS_SNAPSHOTFEATURE_VERSION'
                    value: '1.0.0'
                }
                {
                    name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
                    value: '~2'
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_Java'
                    value: '1'
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_Mode'
                    value: 'recommended'
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_NodeJS'
                    value: '1'
                }
                {
                    name: 'InstrumentationEngine_EXTENSION_VERSION'
                    value: 'disabled'
                }
                {
                    name: 'SnapshotDebugger_EXTENSION_VERSION'
                    value: 'disabled'
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_BaseExtensions'
                    value: 'disabled'
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_PreemptSdk'
                    value: 'disabled'
                }
            ]
        }
    }
    dependsOn: [
        //Some Appsettings depend on resources that must be deployed first
        appInsights
        storageAccount
    ]
    tags: tags
    identity: {
        type: 'SystemAssigned'
    }
}

output principalId string = appService.identity.principalId

My Main bicep file uses the modules as follows:

param location string = resourceGroup().location
param storageAccountSKUName string = 'Standard_LRS'
param functionAppServiceSKUName string = 'Y1'
param logAnalyticsSKUName string = 'Standard'

var storageAccountName = 'mystorage'
var logAnalyticsWorkspaceName = 'mylogs'
var appInsightsName = 'myinsights'
var appServicePlanName = 'myappservice'
var tags = {...}

module storageAccount './modules/storage/StorageAccount.bicep' = {
  name:'storageAccount'
  params: {
    tags: tags
    location:location
    storeAccountName:storageAccountName
    skuName:storageAccountSKUName
  }
}

module logWorkspace './modules/loganalytics/LogWorkspace.bicep' = {
  name: 'logAnalyticsWorkspace'
  params: {
    name: logAnalyticsWorkspaceName
    tags: tags
    skuName: logAnalyticsSKUName
    location: location    
  }
}

module appInsights './modules/loganalytics/AppInsignts.bicep' = {
  name: 'appInsights'
  params: {
    name:appInsightsName
    tags: tags
    location:location    
    workspaceId:logWorkspace.outputs.resourceId
  }
}

module appServicePlan './modules/services/AppServicePlan.bicep' = 
{
    name: 'appServicePlan'
    params: {
        tags: tags
        name: appServicePlanName
        skuName: appServiceSKUName
        location:location
    }
}

module appService './modules/services/AppServiceReceiver.bicep' = {
    name: 'appServiceReceiveHL7v2POI'
    params: {
        tags: tags
        name: appServiceReceiveHL7v2POIName
        location:location
        appServicePlanId:functionServicePlan.outputs.resourceId
        applicationInsightsName:appInsightsName
        storageAccountName:storageAccountName
    }
}

Solution

  • Your AppService module is not waiting for the Storage creation.

    module appService './modules/services/AppServiceReceiver.bicep' = {
        name: 'appServiceReceiveHL7v2POI'
        params: {
            tags: tags
            name: appServiceReceiveHL7v2POIName
            location:location
            appServicePlanId:functionServicePlan.outputs.resourceId
            applicationInsightsName:appInsightsName
            storageAccountName:storageAccountName  //This line is wrong
        }
    }
    

    You need a DependsOn added to the App Service module call. This can be explicit or implicit.

    Explicit (not great practice);

    module appService './modules/services/AppServiceReceiver.bicep' = {
        name: 'appServiceReceiveHL7v2POI'
        params: {
            tags: tags
            name: appServiceReceiveHL7v2POIName
            location:location
            appServicePlanId:functionServicePlan.outputs.resourceId
            applicationInsightsName:appInsightsName
            storageAccountName:storageAccountName
        }
        dependsOn: [
            storageAccount
        ]
    }
    

    or Implicit

    module appService './modules/services/AppServiceReceiver.bicep' = {
        name: 'appServiceReceiveHL7v2POI'
        params: {
            tags: tags
            name: appServiceReceiveHL7v2POIName
            location:location
            appServicePlanId:functionServicePlan.outputs.resourceId
            applicationInsightsName:appInsightsName
            storageAccountName: storageAccount.outputs.name 
        }
    }
    

    You currently have the dependsOn inside the appService module, which is too late. You need it before the module is referenced for the dependency chain to be built properly.