How to automate create Function App with Blob Trigger and Sendgrid Notification through Azure Arm Template and deploy

August 19, 2020

In previous post (Trigger Email on Blob Trigger), we saw how we can create such automation pipeline to get email notification every time someone changes our storage container.

But, that was completely manual.

Lets see how we can create all this with full automation with the help of Azure Arm templates.

Assumptions

Like previous post, this post assumes you have storage containers already with you. This automation does not create any storage account and containers.

Code Repository

Complete code and template files are placed at our Github repo for Azure Template{:target=“_blank”}

Details

This has two parts:

  1. Azure ARM Template code To create all the resources for function app and sendgrid. Code is in folder: azure_arm_template
  2. Actual Function Code A node.js project which just sends email Code is in folder: file_monitor

Azure ARM Template

Lets take a look at Azure Template file{:target=“_blank”}

This template file creates following resources:

  • Azure Function App
    (type: Microsoft.Web/sites)
    With required configuration like Sendgrid API key

  • Server Farm
    (type: Microsoft.Web/serverfarms)

  • Actual Function
    (type: Microsoft.Web/sites/functions)
    This defines the actual bindings from storage container to sendgrid

  • Misc
    Configuration and Bindings

Lets have the complete file here:

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "sites_file_monitor_function_app_name": {
            "defaultValue": "file-monitor-function-app",
            "type": "String"
        },
        "siteName": {
            "type": "string",
            "defaultValue": "[concat('site', uniqueString(resourceGroup().id))]"
        },
        "storageAccountName": {
            "type": "string",
            "defaultValue": "<StorageContainerName>"
        },
        "storageAccountId": {
            "type": "string",
            "defaultValue": "<ResourceIdForStorageContainer>"
        }
    },
    "variables": {
        "hostingPlanName": "[concat(parameters('siteName'),'-plan')]",
        "errorText": "error",
        "appSettingsHelp": "app setting help"
    },
    "resources": [
        {
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "name": "[parameters('sites_file_monitor_function_app_name')]",
            "location": "[resourceGroup().location]",
            "kind": "functionapp",
            "dependsOn": [
                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
              ],
            "properties": {
                "enabled": true,
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAccountName'), ';AccountKey=', listKeys(parameters('storageAccountId'),'2015-05-01-preview').key1)]"
                        },
                        {
                            "name": "FUNCTIONS_WORKER_RUNTIME",
                            "value": "node"
                        },
                        {
                            "name": "WEBSITE_NODE_DEFAULT_VERSION",
                            "value": "10.14.1"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~1"
                        },
                        {
                            "name": "FILE_MONITOR_SENDGRID_APIKEY",
                            "value": "<SENDGRID_API_KEY>"
                        }
                    ]
                },
                "hostNameSslStates": [
                    {
                        "name": "[concat(parameters('sites_file_monitor_function_app_name'), '.azurewebsites.net')]",
                        "sslState": "Disabled",
                        "hostType": "Standard"
                    },
                    {
                        "name": "[concat(parameters('sites_file_monitor_function_app_name'), '.scm.azurewebsites.net')]",
                        "sslState": "Disabled",
                        "hostType": "Repository"
                    }
                ],
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                "reserved": false,
                "isXenon": false,
                "hyperV": false,
                "scmSiteAlsoStopped": false,
                "clientAffinityEnabled": true,
                "clientCertEnabled": false,
                "hostNamesDisabled": false,
                "containerSize": 1536,
                "dailyMemoryTimeQuota": 0,
                "httpsOnly": false,
                "redundancyMode": "None"
            }
        },
        {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2018-02-01",
            "name": "[variables('hostingPlanName')]",
            "location": "[resourceGroup().location]",
            "properties":{  
                "name":"[variables('hostingPlanName')]",
                "computeMode":"Dynamic"
             },
             "sku":{  
                "name":"Y1",
                "tier":"Dynamic",
                "size":"Y1",
                "family":"Y",
                "capacity":0
             }
        },
        {
            "type": "Microsoft.Web/sites/config",
            "apiVersion": "2018-11-01",
            "name": "[concat(parameters('sites_file_monitor_function_app_name'), '/web')]",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', parameters('sites_file_monitor_function_app_name'))]"
            ],
            "properties": {
                "numberOfWorkers": -1,
                "defaultDocuments": [
                    "Default.htm",
                    "Default.html",
                    "Default.asp",
                    "index.htm",
                    "index.html",
                    "iisstart.htm",
                    "default.aspx",
                    "index.php"
                ],
                "netFrameworkVersion": "v4.0",
                "phpVersion": "5.6",
                "requestTracingEnabled": false,
                "remoteDebuggingEnabled": false,
                "httpLoggingEnabled": false,
                "logsDirectorySizeLimit": 35,
                "detailedErrorLoggingEnabled": false,
                "publishingUsername": "$file-monitor-function-app",
                "scmType": "None",
                "use32BitWorkerProcess": true,
                "webSocketsEnabled": false,
                "alwaysOn": false,
                "managedPipelineMode": "Integrated",
                "virtualApplications": [
                    {
                        "virtualPath": "/",
                        "physicalPath": "site\\wwwroot",
                        "preloadEnabled": false
                    }
                ],
                "loadBalancing": "LeastRequests",
                "experiments": {
                    "rampUpRules": []
                },
                "autoHealEnabled": false,
                "cors": {
                    "allowedOrigins": [
                        "https://functions.azure.com",
                        "https://functions-staging.azure.com",
                        "https://functions-next.azure.com"
                    ],
                    "supportCredentials": false
                },
                "localMySqlEnabled": false,
                "ipSecurityRestrictions": [
                    {
                        "ipAddress": "Any",
                        "action": "Allow",
                        "priority": 1,
                        "name": "Allow all",
                        "description": "Allow all access"
                    }
                ],
                "scmIpSecurityRestrictions": [
                    {
                        "ipAddress": "Any",
                        "action": "Allow",
                        "priority": 1,
                        "name": "Allow all",
                        "description": "Allow all access"
                    }
                ],
                "scmIpSecurityRestrictionsUseMain": false,
                "http20Enabled": false,
                "minTlsVersion": "1.2",
                "ftpsState": "AllAllowed",
                "reservedInstanceCount": 0
            }
        },
        {
            "type": "Microsoft.Web/sites/functions",
            "apiVersion": "2018-11-01",
            "name": "[concat(parameters('sites_file_monitor_function_app_name'), '/my-file-monitor')]",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', parameters('sites_file_monitor_function_app_name'))]"
            ],
            "properties": {
                "config": {
                    "bindings": [
                        {
                            "type": "blobTrigger",
                            "direction": "Trigger",
                            "name": "myBlob",
                            "connection":"AzureWebJobsStorage",
                            "path": "gtest/{name}"
                        },
                        {
                            "type": "sendGrid",
                            "direction": "out",
                            "extension": {
                              "id": "Microsoft.Azure.WebJobs.Extensions.SendGrid", "version": "3.0.0"
                            },
                            "name": "$return",
                            "apiKey": "FILE_MONITOR_SENDGRID_APIKEY"
                        }
                    ]
                },
                "test_data": "samples-workitems/workitem.txt"
            }
        },
        {
            "type": "Microsoft.Web/sites/hostNameBindings",
            "apiVersion": "2018-11-01",
            "name": "[concat(parameters('sites_file_monitor_function_app_name'), '/', parameters('sites_file_monitor_function_app_name'), '.azurewebsites.net')]",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', parameters('sites_file_monitor_function_app_name'))]"
            ],
            "properties": {
                "siteName": "file-monitor-function-app-ProfilesPRDLocB",
                "hostNameType": "Verified"
            }
        }
    ]
}

Changes to do before deploying this template

  1. Replace StorageContainerName, and use your desired container name
  2. Replace ResourceIdForStorageContainer and use your resource id. To get it,
    1. Goto your resource group. You can see your storage account.
    2. Click on it.
    3. On the left hand side, you will see option: Properties. Click on it
    4. See section: Storage account resource ID.
    5. Copy that complete string It looks like
      /subscriptions/<your subscription id>/resourceGroups/<resource group>/providers/Microsoft.Storage/storageAccounts/<storage account name>
  3. Replace SENDGRID_API_KEY to have your actual api key for Sendgrid

Deploy Template

I will use command line tool: az for deployment purpose. You need to install this. To install this, refer to: Azure CLI Download{:target=“_blank”}

Lets follow the steps:

1. az login
Run az login
It will take you to browser, ask you to login.
Once login, close your browser tab. On your console, you will see similar message:

You have logged in. Now let us find all the subscriptions to which you have access...
[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "<some id>",
    "id": "<some id>",
    "isDefault": true,
    "managedByTenants": [],
    "name": "<your subscription name>",
    "state": "Enabled",
    "tenantId": "<some id>",
    "user": {
      "name": "<your email>",
      "type": "user"
    }
  },
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "<some id>",
    "id": "<some id>",
    "isDefault": false,
    "managedByTenants": [],
    "name": "<your subscription name>",
    "state": "Enabled",
    "tenantId": "<some id>",
    "user": {
      "name": "<your email>",
      "type": "user"
    }
  },
]

If you have more than one Azure subscription, it will show all of them here. Only one of them will be set by default. See flag: isDefault

If you want to switch to other azure subscription, use following command:

az account set --subscription "your subscription name"

2. Run deployment command

az deployment group create --resource-group "<resource group name>" --template-file azure_arm_template/template.json

If successful, it should show you a success message and list of all the resources it created.

Azure Function App Code

This part has the code for Azure function which performs two things:

  • Setup a trigger on the desired storage container. It acts as a trigger point of our function execution
  • Send Email On each such file change event, we will send an email via Sendgrid

See the code at github folder

Code

Its a node.js project. There are major two files here:

  • index.js
    It has the main code which receives the event and just sends an email.

  • function.js
    This has the two bindings. One for storage container trigger, and other for Sendgrid.

Changes to be done before deployment

  • index.js
    Replace DestinationEmail, and SenderEmail
  • function.js
    Replace StorageContainerName with desired container name.

Create Zip

First go inside folder

Run following command:

zip -r monitor.zip *

Deployment

Run az login if you haven’t done before.

Run following command:

az functionapp deployment source config-zip -g "<resource group name>" -n "Function App Name" --src monitor.zip

If everything succeeds, it will show a success message.

Final Verdict

This code will help you to deploy your resources with no manual efforts. There are some configuration changes which you need to do in above code or template files. Which you can easily bring out in form of parameters.

I hope it helps.


Similar Posts

Latest Posts