Search code examples
msdeployazure-eventgridazure-function-app

Function MSDeploy and Event Grid Subscription Race Condition in ARM Template


I am deploying a function using MSDeploy extensions and then deploying event grid subscription with this function as endpoint. Event grid deployment fails with message -

"details": [
      {
        "code": "Endpoint validation",
        "message": "The attempt to validate the provided azure endpoint resource:/subscriptions/XXXXX/resourceGroups/ResourceGroupName/providers/Microsoft.Web/sites/FunctionAppName/functions/EndpointName failed."
      }
    ]

I believe this is because event grid subscription tried to get created before the function endpoint deployed with MSDeploy is up and running. How can i avoid this race condition? Note: Deploying the same template again creates the event grid fine.

Template being used-

 //function app
        {
            "apiVersion": "2018-11-01",
            "type": "Microsoft.Web/sites",
            "name": "[parameters('functionAppName')]",
            "location": "[resourceGroup().location]",
            "kind": "functionapp",
            "dependsOn": [
                "[variables('azureFunction_serverFarmResourceId')]"
            ],
            "properties": {
                "serverFarmId": "[variables('azureFunction_serverFarmResourceId')]",
                "siteConfig": {
                    "appSettings": [
                        {
                        "name": "AzureWebJobsStorage",
                        "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountResourceId'),variables('storageAccountApiVersion')).keys[0].value)]"
                        },
                        {
                        "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                        "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountResourceId'),variables('storageAccountApiVersion')).keys[0].value)]"//"[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
                        },
                        {
                        "name": "WEBSITE_CONTENTSHARE",
                        "value": "[toLower(parameters('functionAppName'))]"
                        },
                        {
                        "name": "FUNCTIONS_EXTENSION_VERSION",
                        "value": "~3"
                        },
                        {
                        "name": "WEBSITE_NODE_DEFAULT_VERSION",
                        "value": "~10"
                        },
                        {
                        "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                        "value": "[reference(resourceId('microsoft.insights/components/', parameters('functionApp_applicationInsightsName')), '2015-05-01').InstrumentationKey]"
                        },
                        {
                        "name": "FUNCTIONS_WORKER_RUNTIME",
                        "value": "dotnet"
                        }
                    ]
                }
            },
            "resources": [
                {
                    "apiVersion": "2018-11-01",
                    "name": "MSDeploy",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]"

                    ],
                    "properties": {
                        "packageUri": "[parameters('functionAppDeployPackageUri')]"
                    },
                    "type": "extensions"
                }
            ]
        },

        //event grid
        {
            "type": "Microsoft.Storage/storageAccounts/providers/eventSubscriptions",
            "name": "[concat(parameters('storageAccountName'), '/Microsoft.EventGrid/', parameters('blobcreate_eventsubscription_name'))]",
            "apiVersion": "2020-04-01-preview",
            "dependsOn": [
                "[concat('Microsoft.Web/sites/', parameters('functionAppName'), '/extensions/MSDeploy')]",
                "[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]",
                "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
            ],
            "properties": {
                "destination": {
                    "endpointType": "AzureFunction",
                    "properties": {
                        "resourceId": "[concat(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '/functions/', variables('egressDataProcessorFunctionName'))]"
                    }
                },
                "filter": {
                    "subjectBeginsWith": "[concat('/blobServices/default/containers/', parameters('storageAccounts_mainblob_name'))]",  
                    "subjectEndsWith": ".xml",
                    "includedEventTypes": [
                        "Microsoft.Storage.BlobCreated"
                    ],
                    "advancedFilters": []
                },
                "retryPolicy": {
                    "maxDeliveryAttempts": "[parameters('eventgrid_maxDeliveryAttemps')]",
                    "eventTimeToLiveInMinutes": "[parameters('eventgrid_eventTimeToLiveInMinutes')]"
                },
                "deadLetterDestination": {
                    "endpointType": "StorageBlob",
                    "properties": {
                        "resourceId": "[variables('storageAccountResourceId')]",
                        "blobContainerName": "[parameters('storageAccounts_deadletterblob_name')]"
                    }
                }
            }
        }

Solution

  • One way is to deploy your function app as a linked template, and then have your root template:

    1. Deploy the function app template with the function url as an output.
    2. Deploy an Event Grid subscription that depends on the function app deployment and references its output.

    Another possibility is to spit appsettings into a childresource, and have that depend on your MSDeploy resource, then the Event Grid depend on appsettings.