Terraform is an excellent infrastructure as code (IaC) tool for managing Azure resources. However, a configuration drift issue occurs with Function App application settings and Terraform when deploying function app code using Azure pipelines.

In this tutorial, you will deploy a Function App using Terraform and then deploy function code using an Azure pipeline. You will then encounter a problem with the Azure Function application settings with a Terraform update and learn how to avoid it.

For this tutorial, you will need:

  • Terraform open-source executable. This tutorial uses version 1.4.6.
  • Access to an Azure subscription with appropriate permissions, such as Owner or Contributor.
  • An Azure DevOps account.

Deploying an Azure Function App using Terraform

Below is example code on deploying a Function App using Terraform. The Terraform configuration uses the random_pet and random_integer resources to create a unique application name. The configuration continues with deploying the following resources:

  • Resource group
  • App service plan
  • Storage account
  • Windows function app running PowerShell 7

Note: The configuration below uses az login for authentication and stores the state on the local system. You can read more about Azure authentication inside Terraform and storing a remote state in a storage account in the articles below:

Performing Azure Authentication Inside Terraform | Jeff Brown Tech
Move Terraform State to Azure Storage Account | Jeff Brown Tech

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
    }
  }
}
provider "azurerm" {
  features {}
}
resource "random_pet" "name" {
  separator = ""
}
resource "random_integer" "int" {
  min = 10000
  max = 99999
}
locals {
  app_name = "${random_pet.name.id}${random_integer.int.result}"
  location = "westus3"
}
resource "azurerm_resource_group" "rg" {
  name = "${local.app_name}-rg"
  location = local.location
}
resource "azurerm_service_plan" "asp" {
  name = "${local.app_name}-asp"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  os_type = "Windows"
  sku_name = "Y1"
}
resource "azurerm_storage_account" "sa" {
  name = "${local.app_name}sa"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  account_tier = "Standard"
  account_replication_type = "LRS"
}
resource "azurerm_windows_function_app" "funcapp" {
  name = "${local.app_name}-funcapp"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  service_plan_id = azurerm_service_plan.asp.id
  storage_account_name = azurerm_storage_account.sa.name
  storage_account_access_key = azurerm_storage_account.sa.primary_access_key
  site_config {
    application_stack {
      powershell_core_version = "7"
    }
  }
}

The screenshot below shows the resource group with the deployed resources.

terraform azure
Resources deployed using Terraform configuration

Deploying Function App code using Azure DevOps Pipelines

Next, the solution deploys a function in the Function App using an Azure DevOps pipeline. In this example, the function code is already packaged in a .zip file named pwshfuncapp.zip in the repository root. The pipeline triggers on pushes to the main branch.

If you want to try this pipeline deployment yourself, change the values of:

  • azureSubscription to match your service connection name
  • functionAppName to match the application name (random_pet + random_integer)
  • environment to match the target environment (more on environments here)
trigger:
- master
variables:
  # Azure Resource Manager service connection name
  azureSubscription: 'Demo'
  # Base application name (random pet + random integer)
  functionAppName: 'livingewe97058-funcapp'
  # Environment name
  environment: 'Demo-Env'
  # Agent VM image name
  vmImageName: 'windows-2019'
  # Working Directory
  workingDirectory: '$(System.DefaultWorkingDirectory)'
stages:
- stage: Build
  displayName: Build stage
  jobs:
  - job: Build
    displayName: Build
    pool:
      vmImage: $(vmImageName)
    steps:
    - publish: $(System.DefaultWorkingDirectory)/pwshfuncapp.zip
      artifact: drop
- stage: Deploy
  displayName: Deploy stage
  dependsOn: Build
  condition: succeeded()
  jobs:
  - deployment: Deploy
    displayName: Deploy
    environment: $(environment)
    pool:
      vmImage: $(vmImageName)
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureFunctionApp@1
            displayName: 'Azure functions app deploy'
            inputs:
              azureSubscription: '$(azureSubscription)'
              appType: functionApp
              appName: $(functionAppName)
              package: '$(Pipeline.Workspace)/drop/pwshfuncapp.zip'

Once the pipeline completes, the Function App contains a new function named HttpTrigger1.

Function deployed from a pipeline

When you deploy a function app from a pipeline, two new application settings are added to the Function App:

  • WEBSITE_ENABLE_SYNC_UPDATE_SITE
  • WEBSITE_RUN_FROM_PACKAGE

Terraform Application Settings Drift Issue

Everything is looking good. Your infrastructure is defined in code, and your function code is deployed via a pipeline. What could go wrong?

At this point, if you run terraform plan, you should see that Terraform wants to delete the two new application settings created by the pipeline deployment.

terraform azure function application settings drift issue
Terraform detecting changes in application settings

Terraform is typically good at leaving settings alone that it is not managing. However, in this case, Terraform sees these application settings and has decided to remove them because those settings do not exist in its configuration.

You can resolve this by using the lifecycle meta-argument. The lifecycle appears with any resource block regardless of what type the resource block is. One of the arguments within the lifecycle block is ignore_changes, which tells Terraform to ignore changes to specific resource properties.

The solution is to tell Terraform to ignore those application settings. Below is the updated function app resource definition with the added lifecycle block telling Terraform to ignore changes to those application settings.

resource "azurerm_windows_function_app" "funcapp" {
  name                            = "${local.app_name}-funcapp"
  resource_group_name = azurerm_resource_group.rg.name
  location                       = azurerm_resource_group.rg.location
  service_plan_id            = azurerm_service_plan.asp.id
  storage_account_name         = azurerm_storage_account.sa.name
  storage_account_access_key = azurerm_storage_account.sa.primary_access_key
  site_config {
    application_stack {
      powershell_core_version = "7"
    }
  }
  lifecycle {
    ignore_changes = [
      app_settings["WEBSITE_ENABLE_SYNC_UPDATE_SITE"],
      app_settings["WEBSITE_RUN_FROM_PACKAGE"]
    ]
  }
}

After making these changes, Terraform now ignores those settings. Running a new terraform plan should show no changes needed in your deployment.

Related: Review the full code solution at GitHub | Jeff Brown Tech | Terraform_FunctionApp_Example.

Summary

Terraform is a fantastic infrastructure as code solution for managing cloud resources. However, in the case of an Azure Function App, you need to make changes in your Terraform configuration to ensure it does not delete application settings important to your function code deployment process.