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 Name | Used for |
environment_name | the name of the environment ill deploy to |
eai_resource_group | the 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"
}
}