The Terraform azurerm provider now has support for Logic App Standard and I wanted to investigate how to go about setting it up and just generally play around with it and see what I can do.

In this post I want to setup a basic Logic App Standard in Azure with the following:

  • A storage account which is a dependency for the logic app
  • An App Service Plan which is the host for my logic app
  • An App Insights instance which the logic app will send telemetry to
  • A Log Analytics workspace which you would associate with your App Insights instance

Lets just take a look step by step through this.

Add the Terraform Azure RM Provider

You need to tell terraform to add the azure rm provider and configure it like below. I tend to use the below setup which covers most of the stuff Ive done with Terraform for different azure projects and just change the version number to the one you want.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=2.88.1"
    }
    
  }
}


#Configure the Azure Provider
provider "azurerm" {
    skip_provider_registration = true
    features {
        key_vault {
	        recover_soft_deleted_key_vaults = true
            purge_soft_delete_on_destroy = true
        }
    }
}

Create Some Variables

In my script I will have a couple of variables to help me configure the names for the resources. I will also be able to override these if I want to for other environments.

I have 3 variables:

Variable NameUsed for
environment_name the name of the environment ill deploy to
eai_resource_groupthe name of the resource group ill deploy to
context_prefix a prefix ill add to my resources so they are specific to my solution

Below is the snippet for setting up the variables.

variable "environment_name" {
  type = string
  default = "dev"
}

variable "eai_resource_group" {
  type = string
  default = "Platform_LogicApp_Standard"
}

variable "context_prefix" {
  type = string
  default = "ms"
}

Add some data references

Next I will declare some data elements in terraform which are read only pointers to resources in Azure so you can reference properties from them. In this case ill have my azure context, my subscription and the resource group I want to deploy to.

When I do az login and set the subscription then the first 2 references are set automatically.

I then use the variable to get the pointer to my resource group which already exists.

data "azurerm_client_config" "current" {}

data "azurerm_subscription" "current" {}

data "azurerm_resource_group" "eai_resource_group" {
  name     = var.eai_resource_group
}

Create Storage Account

First up we need to create the storage account. I am going to create one storage account for now. You might want to have more than 1 storage account if you have multiple Logic Apps.

The storage account uses the resource group data reference to get its location and where it will be deployed and then we can use terraform syntax to format a name for the resource group.

resource "azurerm_storage_account" "logicapp_std_storage" {
    name                     = "${var.context_prefix}strplatlasta${var.environment_name}"
    resource_group_name      = data.azurerm_resource_group.eai_resource_group.name
    location                 = data.azurerm_resource_group.eai_resource_group.location
    account_tier             = "Standard"
    account_replication_type = "LRS"
}

Create App Service Plan

Next up we need to create an App Service plan to deploy my Logic App to. I can use the normal App Service Plan resource from Terraform’s AzureRM provider. The key difference to where you may have used App Service Plans elsewhere is to specify the WorkflowStandard tier and WS1 size for the sku property.

resource "azurerm_app_service_plan" "platform_logicapp_plan" {
    name                    = "${var.context_prefix}-asp-platform-logicapps-${var.environment_name}"
    location                = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name     = data.azurerm_resource_group.eai_resource_group.name
    kind                    = "elastic"
    is_xenon                = "false"
    per_site_scaling        = "false"
    reserved                = "false"
    tags                    = {}
    zone_redundant          = "false"
    sku {
        tier = "WorkflowStandard"
        size = "WS1"
    }
}

Create Log Analytics Workspace

Next I will create a log analytics workspace. It is the default use of the log analytics resource in terraform and I am adding it so when I create my app insights instance next I can point it to this Log Analytics workspace.

resource "azurerm_log_analytics_workspace" "platform_logicapp_logs" {
    name                = "${var.context_prefix}-log-platform-logicapps-${var.environment_name}"
    location            = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name = data.azurerm_resource_group.eai_resource_group.name
    sku                 = "PerGB2018"
    retention_in_days   = 30
}

Create App Insights

Next I add my app insights instance. To make this a workspace based type I add the workspace id property like below.

resource "azurerm_application_insights" "platform_logicapp_appinsights" {
    name                = "${var.context_prefix}-ai-platform-logicapps-${var.environment_name}"
    location            = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name = data.azurerm_resource_group.eai_resource_group.name
    application_type    = "web"
    workspace_id        = azurerm_log_analytics_workspace.platform_logicapp_logs.id
}

Create Logic App

I am now ready to create my Logic App. In this resource I can point to the resource group settings like we did for other resources. I can also link it to my app service plan by referencing the id property from the app service plan we will create.

I can add the link to the storage account again linking to the storage resource we created earlier.

I can also choose to tighten a couple of settings to I have set it to support https only and disabled the sftp deployment capability. I can also configure some performance settings such as the scale limit and pre-warmed instance.

I can also have it configure a managed identity simply by setting the identity type.

resource "azurerm_logic_app_standard" "helloworld" {
    name                        = "${var.context_prefix}-la-hello-world-${var.environment_name}"
    location                    = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name         = data.azurerm_resource_group.eai_resource_group.name
    app_service_plan_id         = azurerm_app_service_plan.platform_logicapp_plan.id
    storage_account_name        = azurerm_storage_account.logicapp_std_storage.name
    storage_account_access_key  = azurerm_storage_account.logicapp_std_storage.primary_access_key
    storage_account_share_name  = "${var.context_prefix}-la-hello-world-${var.environment_name}"
    
    https_only                  = true
    version                     = "~3"

    site_config {
        always_on                   = false
        dotnet_framework_version    = "v4.0"
        ftps_state                  = "Disabled"
        pre_warmed_instance_count   = "0"
        app_scale_limit             = "1"
    }

    identity  {
        type                        = "SystemAssigned"
    }
}

Running Terraform

The full script is below if you want it, but to run it I simply run the following commands when my vs code terminal is pointing to the directory with my terraform configuration in it.

az login

az account set --subscription [your sub id here]

terraform init

terraform validate

terraform apply

Once my terraform is finished building my environment I am left with the below setup in Azure.

My next step is to try it out.

Initial Feedback

The first thing I noticed was that If I try to create a new workflow in the browser then I get an error saving the workflow.

If I try to deploy a logic app from VS code I also get an error like below.

The problem here is that with the script I used I didnt specify the functions worker runtime to use. I just added the below config value then it worked fine.

“FUNCTIONS_WORKER_RUNTIME” = “node”

This error brought up a great point for how to manage app settings.

In the azurerm_logic_app_standard resource in terraform you can manage the app settings and connection strings elements however I think you need to be careful here as you could cause problems for yourself.

If you dont use the app settings or connection strings in terraform and completely manage them with your logic app code deployment then I think it will work fine. Terraform will ignore the below list of app settings:

  • WEBSITE_CONTENTAZUREFILECONNECTIONSTRING
  • APP_KIND
  • AzureFunctionsJobHost__extensionBundle__id
  • AzureFunctionsJobHost__extensionBundle__version
  • AzureWebJobsDashboard
  • AzureWebJobsStorage
  • FUNCTIONS_EXTENSION_VERSION
  • WEBSITE_CONTENTSHARE

But if you add an app settings block and add any other settings then you run the risk of them being considered creep and being removed. An easy way to see this problem in action if if we use the below snippet in the logic app app settings part of the terraform resource.

If I apply then I will add this setting. If I next go to Azure and add a setting called Setting-NOT-From-Terraform in via the portal and save it. I then run a refresh and the setting will be brought into my Terraform state. I then run an apply and Terraform will assume you are telling it the desired state which is in your terraform configuration and it will detect that it should remove the setting I added manually which you can see below.

I think for Logic App solutions this will be a scenario where we will use app settings a lot in our custom code so its probably better to just ignore setting app settings and config settings via terraform as I think you will give yourself additional overhead having to sync things up everytime new settings are added.

One option to consider is the use of the ignore lifecycle changes feature in Terraform. I havent tried this for Logic Apps yet and may check it out sometime to see if it works but essentially you would add some app settings when setting up the logic app then you would use a snippet like below in the azurerm_logic_app_standard resource and it should ignore app settings in future.

lifecycle {
    ignore_changes = [
      app_settings
    ]
  }

Full Script

The full script I have used here is below. I normally split bits out across multiple files and terraform will automatically parse any .tf files in the directory and workout what to do but to help get you started you can just put the full thing below in a .tf file and then run the commands and it will get you going.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=2.88.1"
    }
    
  }
}


#Configure the Azure Provider
provider "azurerm" {
    skip_provider_registration = true
    features {
        key_vault {
	        recover_soft_deleted_key_vaults = true
            purge_soft_delete_on_destroy = true
        }
    }
}


variable "environment_name" {
  type = string
  default = "dev"
}

variable "eai_resource_group" {
  type = string
  default = "Platform_LogicApp_Standard"
}

variable "context_prefix" {
  type = string
  default = "ms"
}


data "azurerm_client_config" "current" {}

data "azurerm_subscription" "current" {}

data "azurerm_resource_group" "eai_resource_group" {
  name     = var.eai_resource_group
}


#Create a storage account to be used by the logic apps
resource "azurerm_storage_account" "logicapp_std_storage" {
    name                     = "${var.context_prefix}strplatlasta${var.environment_name}"
    resource_group_name      = data.azurerm_resource_group.eai_resource_group.name
    location                 = data.azurerm_resource_group.eai_resource_group.location
    account_tier             = "Standard"
    account_replication_type = "LRS"
}

#Create a plan for the logic apps to run on
resource "azurerm_app_service_plan" "platform_logicapp_plan" {
    name                    = "${var.context_prefix}-asp-platform-logicapps-${var.environment_name}"
    location                = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name     = data.azurerm_resource_group.eai_resource_group.name
    kind                    = "elastic"
    is_xenon                = "false"
    per_site_scaling        = "false"
    reserved                = "false"
    tags                    = {}
    zone_redundant          = "false"
    sku {
        tier = "WorkflowStandard"
        size = "WS1"
    }
}

#Create a log analytics workspace for use by logic apps and app insights in workspace mode
resource "azurerm_log_analytics_workspace" "platform_logicapp_logs" {
    name                = "${var.context_prefix}-log-platform-logicapps-${var.environment_name}"
    location            = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name = data.azurerm_resource_group.eai_resource_group.name
    sku                 = "PerGB2018"
    retention_in_days   = 30
}

#Create an app insights instance for the logic apps to send telemetry to
resource "azurerm_application_insights" "platform_logicapp_appinsights" {
    name                = "${var.context_prefix}-ai-platform-logicapps-${var.environment_name}"
    location            = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name = data.azurerm_resource_group.eai_resource_group.name
    application_type    = "web"
    workspace_id        = azurerm_log_analytics_workspace.platform_logicapp_logs.id
}

#Create a Logic App on the plan
resource "azurerm_logic_app_standard" "helloworld" {
    name                        = "${var.context_prefix}-la-hello-world-${var.environment_name}"
    location                    = data.azurerm_resource_group.eai_resource_group.location
    resource_group_name         = data.azurerm_resource_group.eai_resource_group.name
    app_service_plan_id         = azurerm_app_service_plan.platform_logicapp_plan.id
    storage_account_name        = azurerm_storage_account.logicapp_std_storage.name
    storage_account_access_key  = azurerm_storage_account.logicapp_std_storage.primary_access_key
    storage_account_share_name  = "${var.context_prefix}-la-hello-world-${var.environment_name}"
    
    https_only                  = true
    version                     = "~3"

    site_config {
        always_on                   = false
        dotnet_framework_version    = "v4.0"
        ftps_state                  = "Disabled"
        pre_warmed_instance_count   = "0"
        app_scale_limit             = "1"
    }

    identity  {
        type                        = "SystemAssigned"
    }
}




 

Buy Me A Coffee