2 min read

Trick of Deploying Function App with Terraform

Azure Function App is a powerful service. It offers various hosting options to fit a multitude of needs. I try to incorporate them into my solutions wherever I can, due to the savings they provide in development and maintenance. They may also vastly increase the speed of development which is key in meeting business needs.

Most of my Cloud automation is done through Terraform. I learned Terraform when I started my cloud journey a few years ago with Amazon Web Services(AWS). Later when I switched focus to Microsoft Azure in 2018, I continued using Terraform. At that time, the support for Azure in Terraform was not great. Ever since, it has improved dramtically.

Please note that the Terraform configuration I provide in this article is not complete: You will most likely be adding further arguements to each resource. This is just to get you started and illustrate a point later.

With that out of the way, a very basic deployment of a Function App in Terraform can be something like:

resource "azurerm_service_plan" "serviceplan" {
  name                = "serviceplan"
  resource_group_name = "resourcegroupname"
  location            = "location"
  os_type             = "Linux"
  sku_name            = "Y1"
}

resource "azurerm_linux_function_app" "cool_new_app" {
  name                = "coolnewapp"
  resource_group_name = "resourcegroupname"
  location            = "location"

  storage_account_name       = "storageaccountname"
  storage_account_access_key = "storageaccountkey"
  service_plan_id            = azurerm_service_plan.serviceplan.id
}

After the Function App is deployed, you may proceed to deploy the actual code using the "zip deploy".

However, there is a serious problem: You will notice that everytime you run this Terraform configuration, your Function App is going to be emptied of your code! This is because Terraform will override the "WEBSITE_RUN_FROM_PACKAGE" configuration item in your Function App. This configuration item is needed for the Function App to find your code in the Storage Account.

To prevent above from happening, we are going to:

  1. Fetch the existing configuration item using a Terraform data source
  2. Safely populate a localwith the existing configuration
  3. Merge the existing configuration when deploying the Function App

So, let's modify our Terraform configuration according to the above.

Fetch the existing configuration item using a Terraform data source:

data "azurerm_linux_function_app" "existing" {
  name                = local.function_app_name
  resource_group_name = "resourcegroupname"
}

Safely populate a localwith the existing configuration:

locals {
  existing_app_settings = coalesce(data.azurerm_linux_function_app.existing.app_settings, {})
  function_app_name     = "coolnewapp"
}

Merge the existing configuration when deploying the Function App:

resource "azurerm_service_plan" "serviceplan" {
  name                = "serviceplan"
  resource_group_name = "resourcegroupname"
  location            = "location"
  os_type             = "Linux"
  sku_name            = "Y1"
}

resource "azurerm_linux_function_app" "cool_new_app" {
  name                = local.function_app_name
  resource_group_name = "resourcegroupname"
  location            = "location"

  storage_account_name       = "storageaccountname"
  storage_account_access_key = "storageaccountkey"
  service_plan_id            = azurerm_service_plan.serviceplan.id

  // [Start Workaround to prevent Terraform from "removing" function code]
  app_settings = merge(
    {
      "WEBSITE_RUN_FROM_PACKAGE" = lookup(local.existing_app_settings, "WEBSITE_RUN_FROM_PACKAGE", null)
    },
    {
      "AppSettingKey" = "AppSettingValue"
    }
  )
  // [End Workaround to prevent Terraform "removing" function code]
}

Happy coding!