Anthony Chu Contact Me

ARM Template - Azure Web App with Storage and SendGrid

Thursday, November 12, 2015

Azure Resource Manager templates are extremely useful. Today we'll look at how to use a template to provision accounts for Azure Storage and SendGrid, and configure an Azure Web App to use them.

The template we'll be creating will provision these resources:

  • App Service Plan
  • Web App
  • Storage account
  • SendGrid account
  • Web App connection strings for the Storage Account
  • Web App app settings for SendGrid server and credentials

Dependencies

ARM templates makes managing resource dependencies easy. When set up properly, it will provision resources in parallel as much as possible.

There are 3 dependencies in our template:

  • The Web App can't be created until the App Service Plan is created
  • The Web App connection strings must wait until the Storage account and Web App are created
  • The Web App app settings must wait until the SendGrid account and Web App are created

Note that the Web App is not dependent on the Storage and SendGrid account, so they can all be created in parallel.

{
    "name": "[parameters('webAppName')]",
    "type": "Microsoft.Web/sites",
    "location": "[resourceGroup().location]",
    "apiVersion": "2014-06-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms', parameters('svcPlanName'))]"
    ],
    // ...
},
// ...
{
    "type": "config",
    "name": "appsettings",
    "apiVersion": "2015-08-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', parameters('webAppName'))]",
        "[resourceId('Sendgrid.Email/accounts', parameters('sendgridAccountName'))]"
    ],
    // ...
},
{
    "type": "config",
    "name": "connectionstrings",
    "apiVersion": "2015-08-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', parameters('webAppName'))]",
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]"
    ],
    // ...
}

Storage Account and the listKeys() Function

We want to provision our Storage account and use the generated primary access key to populate a couple of connection strings in the web app. ARM has a function called listKeys() that makes this possible... without the need for the user to know the key or for the key to appear in the template.

First, we declare the Storage account:

{
    "name": "[parameters('storageName')]",
    "type": "Microsoft.Storage/storageAccounts",
    "location": "[resourceGroup().location]",
    "apiVersion": "2015-05-01-preview",
    "tags": {
        "displayName": "[parameters('storageName')]"
    },
    "properties": {
        "accountType": "[parameters('storageType')]"
    }
}

Then we use listKeys() to grab the access key and create our connection strings:

{
    "type": "config",
    "name": "connectionstrings",
    "apiVersion": "2015-08-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', parameters('webAppName'))]",
        "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]"
    ],
    "properties": {
        "AzureWebJobsDashboard": {
            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]",
            "type": "Custom"
        },
        "AzureWebJobsStorage": {
            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]",
            "type": "Custom"
        }
    }
}

SendGrid Account and the reference() Function

Not only can ARM templates create native Azure resources, they can be used to create some third party services too. One example is SendGrid. SendGrid offers a free SMTP account for use with Azure that includes up to 25,000 emails sent per month.

We want to provision a SendGrid account, and then create app settings in the Web App for the SendGrid server, username, and password.

Here's the template section for the SendGrid account:

{
    "name": "[parameters('sendgridAccountName')]",
    "type": "Sendgrid.Email/accounts",
    "location": "[resourceGroup().location]",
    "apiVersion": "2015-01-01",
    "plan": {
        "name": "free",
        "publisher": "Sendgrid",
        "product": "sendgrid_azure",
        "promotionCode": ""
    },
    "properties": {
        "password": "[parameters('sendgridPassword')]",
        "acceptMarketingEmails": "0",
        "email": ""
    }
}

To access the properties of the created account, we'll use the reference() ARM Template function:

{
    "type": "config",
    "name": "appsettings",
    "apiVersion": "2015-08-01",
    "dependsOn": [
        "[resourceId('Microsoft.Web/sites/', parameters('webAppName'))]",
        "[resourceId('Sendgrid.Email/accounts', parameters('sendgridAccountName'))]"
    ],
    "properties": {
        "toAddress": "[parameters('toAddress')]",
        "sendgridSmtpServer": "[reference(resourceId('Sendgrid.Email/accounts', parameters('sendgridAccountName'))).smtpServer]",
        "sendgridUsername": "[reference(resourceId('Sendgrid.Email/accounts', parameters('sendgridAccountName'))).username]",
        "sendgridPassword": "[parameters('sendgridPassword')]"
    }
}

Outputs Section

So the reference() function is great, but how do we know what properties are on the resource object that is returned? We can add an outputs section to our ARM template and output our resource so we can look at its properties.

Here, we're outputting the SendGrid account resource:

"outputs": {
    "SendGrid": {
        "type": "object",
        "value": "[reference(resourceId('Sendgrid.Email/accounts', parameters('sendgridAccountName')))]"
    }
}

One way to see the output is in the Azure Portal. In the Resource Group blade, click on "Last Deployment" and navigate to the latest deployment:

Here's the output JSON that tells us what properties are available on the SendGrid resource:

{
    "email": "",
    "provisioningState": "Succeeded",
    "status": "Running",
    "username": "azure_*****************@azure.com",
    "smtpServer": "smtp.sendgrid.net",
    "planLabel": "Free"
}

Source Code

The full template is available here.