Repository: coralogix/terraform-coralogix-azure
Pull request #23: feat: CDS-1933 add new module blobotel
Author: login: cazorla19
Created: 2025-03-27T09:56:12Z | Merged: 2025-03-27T14:06:24Z

# Description

This will add the Terraform module for BlobToOtel Azure Function: https://github.com/coralogix/coralogix-azure-serverless/pull/49

# How Has This Been Tested?

Ran the module instance locally, verified it works as expected.

# Checklist:
- [x] I have updated the relevant component changelog(s)
- [ ] This change does not affect any particular component (e.g. it's readme or docs change)


Diff:
diff --git a/README.md b/README.md
index 513acf7..0c1df1c 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,23 @@ module "eventhub" {
 }
 ```
 
+`blobtootel`:
+
+```hcl
+module "blobtootel" {
+  source = "coralogix/azure/coralogix//modules/blobtootel"
+
+  OtelEndpoint                   = < OTLP Endpoint >
+  FunctionResourceGroupName      = < Function ResourceGroup Name >
+  FunctionAppServicePlanType     = "Consumption"
+  EventHubNamespace              = < Name of Eventhub Namespace >
+  EventHubName                   = < Name of EventHub Instance >
+  EventHubResourceGroup          = < Name of Eventhub ResourceGroup >
+  BlobContainerStorageAccount    = < Blob Container Storage Account Name >
+  BlobContainerResourceGroupName = < Blob Container Resource Group Name >
+}
+```
+
 `blobstorage`:
 
 ```hcl
diff --git a/modules/blobtootel/CHANGELOG.md b/modules/blobtootel/CHANGELOG.md
new file mode 100644
index 0000000..06e1ecc
--- /dev/null
+++ b/modules/blobtootel/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+## blobtootel
+<!-- To add a new entry write: -->
+<!-- ### version / full date -->
+<!-- * [Update/Bug fix] message that describes the changes that you apply -->
+
+### 1.0.0 / 27 Mar 2025
+
+* [New] Initial release.
diff --git a/modules/blobtootel/README.md b/modules/blobtootel/README.md
new file mode 100644
index 0000000..52612d8
--- /dev/null
+++ b/modules/blobtootel/README.md
@@ -0,0 +1,95 @@
+# Blob to OTEL
+
+Manage the function app which reads logs from `Blobs` in your account storage and sends them to your OTEL endpoint.
+
+## Pre-requisites
+
+A Resource Group, Storage Account and EventHub must be provided as inputs to the Terraform module. Additionally, event notifications to the EventHub must be configured for the Storage Account.
+
+If you are using a Premium App Service Plan, you must also provide a vNet and subnet ID.
+
+Resource Groups for the Storage Account, EventHub and Function App must be provided as inputs to the Terraform module in separate variables.
+
+## Usage
+
+```hcl
+terraform {
+  required_providers {
+    azurerm = {
+      source = "hashicorp/azurerm"
+      version = "~> 3.93"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}
+
+# Standard version without vNet integration
+module "blobtootel-standard" {
+  source = "coralogix/azure/coralogix//modules/blobtootel"
+
+  OtelEndpoint                   = "my-otel-endpoint.com:4318"
+  FunctionResourceGroupName      = "my-function-resource-group"
+  FunctionAppServicePlanType     = "Consumption"
+  EventHubNamespace              = "my-eventhub-namespace"
+  EventHubName                   = "my-eventhub-name"
+  EventHubResourceGroup          = "my-eventhub-resource-group"
+  BlobContainerStorageAccount    = "my-blob-container-storage-account"
+  BlobContainerResourceGroupName = "my-blob-container-resource-group"
+}
+
+# Premium version with vNet integration
+module "blobtootel-vnet" {
+  source = "coralogix/azure/coralogix//modules/blobtootel"
+
+  OtelEndpoint                   = "my-otel-endpoint.com:4318"
+  FunctionResourceGroupName      = "my-function-resource-group"
+  FunctionAppServicePlanType     = "Premium"
+  EventHubNamespace              = "my-eventhub-namespace"
+  EventHubName                   = "my-eventhub-name"
+  EventHubResourceGroup          = "my-eventhub-resource-group"
+  BlobContainerStorageAccount    = "my-blob-container-storage-account"
+  BlobContainerResourceGroupName = "my-blob-container-resource-group"
+  VirtualNetworkName             = "my-virtual-network"
+  SubnetName                     = "my-subnet"
+  VirtualNetworkResourceGroup    = "my-virtual-network-resource-group"
+}
+```
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.7.4 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | >= 3.93 |
+| <a name="provider_random"></a> [random](#provider\_random) | >= 3.4.3 |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| <a name="input_OtelEndpoint"></a> [OtelEndpoint](#input\_OtelEndpoint) | The OTLP endpoint to send the logs to. | `string` | n/a | yes |
+| <a name="input_CoralogixDirectMode"></a> [CoralogixDirectMode](#input\_CoralogixDirectMode) | Whether to use Coralogix direct mode or not. | `string` | `"false"` | no |
+| <a name="input_CoralogixApiKey"></a> [CoralogixApiKey](#input\_CoralogixApiKey) | Your Coralogix Send Your Data - API Key. Used in case of using Coralogix as an OTLP endpoint. | `string` | `""` | no |
+| <a name="input_CoralogixApplication"></a> [CoralogixApplication](#input\_CoralogixApplication) | The name of the Application in Coralogix. | `string` | `"azure"` | no |
+| <a name="input_CoralogixSubsystem"></a> [CoralogixSubsystem](#input\_CoralogixSubsystem) | The name of the Subsystem in Coralogix. | `string` | `"blob-storage-logs"` | no |
+| <a name="input_NewlinePattern"></a> [NewlinePattern](#input\_NewlinePattern) | The pattern that separates the lines in the blob. | `string` | `"(?:\r\n|\r|\n)"` | no |
+| <a name="input_PrefixFilter"></a> [PrefixFilter](#input\_PrefixFilter) | The prefix filter to apply to the blob container. Use 'NoFilter' to not filter by prefix. Wildcards are not allowed. Use the following format 'subfolder1/subfolder2/'. | `string` | `"NoFilter"` | no |
+| <a name="input_SuffixFilter"></a> [SuffixFilter](#input\_SuffixFilter) | The suffix filter to apply to the blob container. Use 'NoFilter' to not filter by suffix. Wildcards are not allowed. Use the following format '.log'. | `string` | `"NoFilter"` | no |
+| <a name="input_FunctionResourceGroupName"></a> [FunctionResourceGroupName](#input\_FunctionResourceGroupName) | The name of the resource group into which to deploy the Function App | `string` | n/a | yes |
+| <a name="input_FunctionAppServicePlanType"></a> [FunctionAppServicePlanType](#input\_FunctionAppServicePlanType) | The type of the App Service Plan to use for the Function App | `string` | `"Consumption"` | no |
+| <a name="input_EventHubNamespace"></a> [EventHubNamespace](#input\_EventHubNamespace) | The name of the Event Hub Namespace. | `string` | n/a | yes |
+| <a name="input_EventHubName"></a> [EventHubName](#input\_EventHubName) | The name of the Event Hub. | `string` | n/a | yes |
+| <a name="input_EventHubResourceGroup"></a> [EventHubResourceGroup](#input\_EventHubResourceGroup) | The name of the resource group that contains the Event Hub Namespace. | `string` | n/a | yes |
+| <a name="input_BlobContainerStorageAccount"></a> [BlobContainerStorageAccount](#input\_BlobContainerStorageAccount) | The name of the Storage Account containing the Blob Container. | `string` | n/a | yes |
+| <a name="input_BlobContainerResourceGroupName"></a> [BlobContainerResourceGroupName](#input\_BlobContainerResourceGroupName) | The name of the resource group that contains the Storage Account | `string` | n/a | yes |
+| <a name="input_VirtualNetworkName"></a> [VirtualNetworkName](#input\_VirtualNetworkName) | Name of the Virtual Network (leave empty if VNet integration is not needed) | `string` | `""` | no |
+| <a name="input_SubnetName"></a> [SubnetName](#input\_SubnetName) | Name of the Subnet (leave empty if VNet integration is not needed) | `string` | `""` | no |
+| <a name="input_VirtualNetworkResourceGroup"></a> [VirtualNetworkResourceGroup](#input\_VirtualNetworkResourceGroup) | The name of the resource group that contains the Virtual Network | `string` | `""` | no |
diff --git a/modules/blobtootel/main.tf b/modules/blobtootel/main.tf
new file mode 100644
index 0000000..740e5aa
--- /dev/null
+++ b/modules/blobtootel/main.tf
@@ -0,0 +1,143 @@
+locals {
+  function_name = join("-", ["BlobToOtel", substr(var.BlobContainerStorageAccount, 0, 27), random_string.this.result])
+  storage_name  = lower(substr(replace(join("", ["fn", var.BlobContainerStorageAccount, random_string.this.result]), "-", ""), 0, 24))
+  sku           = var.FunctionAppServicePlanType == "Consumption" ? "Y1" : "EP1"
+}
+
+resource "random_string" "this" {
+  length  = 5
+  special = false
+  lower   = true
+  upper   = false
+}
+
+# ------------------------------------------------ Blob Storage ------------------------------------------------
+
+data "azurerm_resource_group" "blobtootel-resourcegroup" {
+  name = var.BlobContainerResourceGroupName
+}
+
+data "azurerm_storage_account" "blobtootel-storageaccount" {
+  name                = var.BlobContainerStorageAccount
+  resource_group_name = var.BlobContainerResourceGroupName
+}
+
+# ------------------------------------------------ EventHub ------------------------------------------------
+
+data "azurerm_eventhub_namespace" "eventhub-namespace" {
+  name                = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub" "eventhub" {
+  name                = var.EventHubName
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub_namespace_authorization_rule" "eventhub-namespace-auth" {
+  name                = "RootManageSharedAccessKey" # This is the default rule name
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+resource "azurerm_eventhub_consumer_group" "eventhub-consumergroup" {
+  name                = local.function_name
+  namespace_name      = var.EventHubNamespace
+  eventhub_name       = var.EventHubName
+  resource_group_name = var.EventHubResourceGroup
+}
+
+# ------------------------------------------------ vNet Integration ------------------------------------------------
+
+# Add data source for subnet (only if Premium plan is selected)
+data "azurerm_subnet" "function_subnet" {
+  count                = var.FunctionAppServicePlanType == "Premium" ? 1 : 0
+  name                 = var.SubnetName
+  virtual_network_name = var.VirtualNetworkName
+  resource_group_name  = var.VirtualNetworkResourceGroup
+}
+
+# ------------------------------------------------ Function App ------------------------------------------------
+
+data "azurerm_resource_group" "functionRG" {
+  name = var.FunctionResourceGroupName
+}
+
+resource "azurerm_storage_account" "functionSA" {
+  name                     = local.storage_name
+  resource_group_name      = var.FunctionResourceGroupName
+  location                 = data.azurerm_resource_group.functionRG.location
+  account_tier             = "Standard"
+  account_replication_type = "LRS"
+}
+
+resource "azurerm_service_plan" "service-plan" {
+  name                = "${local.function_name}-plan"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  os_type             = "Linux"
+  sku_name            = local.sku
+}
+
+resource "azurerm_log_analytics_workspace" "crx-workspace" {
+  name                = "${local.function_name}-workspace"
+  location            = data.azurerm_resource_group.functionRG.location
+  resource_group_name = var.FunctionResourceGroupName
+  sku                 = "PerGB2018"
+  retention_in_days   = 90
+}
+
+resource "azurerm_application_insights" "crx-appinsights" {
+  name                = "${local.function_name}-appinsights"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  workspace_id        = azurerm_log_analytics_workspace.crx-workspace.id
+  application_type    = "web"
+}
+
+resource "azurerm_linux_function_app" "blobtootel-function" {
+  name                        = local.function_name
+  resource_group_name         = var.FunctionResourceGroupName
+  location                    = data.azurerm_resource_group.functionRG.location
+  storage_account_name        = azurerm_storage_account.functionSA.name
+  storage_account_access_key  = azurerm_storage_account.functionSA.primary_access_key
+  service_plan_id             = azurerm_service_plan.service-plan.id
+  functions_extension_version = "~4"
+  virtual_network_subnet_id   = var.FunctionAppServicePlanType == "Premium" ? data.azurerm_subnet.function_subnet[0].id : null
+  site_config {
+    application_stack {
+      node_version = 20
+    }
+  }
+  app_settings = {
+    OTEL_EXPORTER_OTLP_ENDPOINT              = var.OtelEndpoint
+    CORALOGIX_DIRECT_MODE                    = var.CoralogixDirectMode
+    CORALOGIX_API_KEY                        = var.CoralogixApiKey
+    CORALOGIX_APPLICATION                    = var.CoralogixApplication
+    CORALOGIX_SUBSYSTEM                      = var.CoralogixSubsystem
+    EVENT_HUB_NAME                           = var.EventHubName
+    NEWLINE_PATTERN                          = var.NewlinePattern
+    PREFIX_FILTER                            = var.PrefixFilter
+    SUFFIX_FILTER                            = var.SuffixFilter
+    BLOB_STORAGE_ACCOUNT_CONNECTION_STRING   = data.azurerm_storage_account.blobtootel-storageaccount.primary_connection_string
+    EVENT_HUB_NAMESPACE_CONNECTION_STRING    = data.azurerm_eventhub_namespace_authorization_rule.eventhub-namespace-auth.primary_connection_string
+    AzureWebJobsStorage                      = azurerm_storage_account.functionSA.primary_connection_string
+    APPLICATIONINSIGHTS_CONNECTION_STRING    = azurerm_application_insights.crx-appinsights.connection_string
+    WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTSHARE                     = lower(local.function_name)
+    FUNCTIONS_EXTENSION_VERSION              = "~4"
+    FUNCTIONS_WORKER_RUNTIME                 = "node"
+    WEBSITE_RUN_FROM_PACKAGE                 = "https://coralogix-public.s3.eu-west-1.amazonaws.com/azure-functions-repo/BlobToOtel.zip"
+  }
+}
+
+# ------------------------------------------------ Output ------------------------------------------------
+
+output "RegionCheck" {
+  value = data.azurerm_resource_group.functionRG.location == data.azurerm_resource_group.blobtootel-resourcegroup.location ? "[Info] Azure Function WAS deployed in the same region as the Storage Blob" : "[Notice] Azure Function WAS NOT deployed in the same region as the Storage Blob"
+}
+
+output "SyncTriggerCommand" {
+  value = "Run this command to sync your StorageQueue Triggers:\n\taz resource invoke-action -g ${var.FunctionResourceGroupName} -n ${local.function_name} --action syncfunctiontriggers --resource-type Microsoft.Web/sites"
+}
diff --git a/modules/blobtootel/variables.tf b/modules/blobtootel/variables.tf
new file mode 100644
index 0000000..d7a797a
--- /dev/null
+++ b/modules/blobtootel/variables.tf
@@ -0,0 +1,109 @@
+variable "OtelEndpoint" {
+  description = "Your OTLP endpoint URL (example: https://my-api-endpoint:443)."
+  type        = string
+}
+
+variable "CoralogixDirectMode" {
+  description = "Whether to use Coralogix as an OTLP endpoint."
+  type        = string
+  default     = "false"
+  validation {
+    condition     = contains(["true", "false"], var.CoralogixDirectMode)
+    error_message = "The coralogix direct mode must be either 'true' or 'false'."
+  }
+}
+
+variable "CoralogixApiKey" {
+  description = "Your Coralogix Send Your Data - API Key. Used in case of using Coralogix as an OTLP endpoint."
+  type        = string
+  default     = ""
+  sensitive   = true
+}
+
+variable "CoralogixApplication" {
+  description = "The name of the Application in Coralogix."
+  type        = string
+  default     = "azure"
+}
+
+variable "CoralogixSubsystem" {
+  description = "The name of the Subsystem in Coralogix."
+  type        = string
+  default     = "blob-storage-logs"
+}
+
+variable "NewlinePattern" {
+  description = "The pattern that separates the lines in the blob."
+  type        = string
+  default     = "(?:\r\n|\r|\n)"
+}
+
+variable "PrefixFilter" {
+  description = "The prefix filter to apply to the blob container. Use 'NoFilter' to not filter by prefix. Wildcards are not allowed. Use the following format 'subfolder1/subfolder2/'."
+  type        = string
+  default     = "NoFilter"
+}
+
+variable "SuffixFilter" {
+  description = "The suffix filter to apply to the blob container. Use 'NoFilter' to not filter by suffix. Wildcards are not allowed. Use the following format '.log'."
+  type        = string
+  default     = "NoFilter"
+}
+
+variable "FunctionResourceGroupName" {
+  description = "The name of the resource group into which to deploy the Function App"
+  type        = string
+}
+
+variable "FunctionAppServicePlanType" {
+  description = "The type of the App Service Plan to use for the Function App"
+  type        = string
+  default     = "Consumption"
+  validation {
+    condition     = contains(["Consumption", "Premium"], var.FunctionAppServicePlanType)
+    error_message = "The function app service plan type must be on of these values: [Consumption, Premium]."
+  }
+}
+
+variable "EventHubNamespace" {
+  description = "The name of the Event Hub Namespace."
+  type        = string
+}
+
+variable "EventHubName" {
+  description = "The name of the Event Hub."
+  type        = string
+}
+
+variable "EventHubResourceGroup" {
+  description = "The name of the resource group that contains the Event Hub Namespace."
+  type        = string
+}
+
+variable "BlobContainerStorageAccount" {
+  description = "The name of the Storage Account containing the Blob Container."
+  type        = string
+}
+
+variable "BlobContainerResourceGroupName" {
+  description = "The name of the resource group that contains the Storage Account"
+  type        = string
+}
+
+variable "VirtualNetworkName" {
+  description = "Name of the Virtual Network (leave empty if VNet integration is not needed)"
+  type        = string
+  default     = ""
+}
+
+variable "SubnetName" {
+  description = "Name of the Subnet (leave empty if VNet integration is not needed)"
+  type        = string
+  default     = ""
+}
+
+variable "VirtualNetworkResourceGroup" {
+  description = "The name of the resource group that contains the Virtual Network"
+  type        = string
+  default     = ""
+}
diff --git a/modules/blobtootel/versions.tf b/modules/blobtootel/versions.tf
new file mode 100644
index 0000000..8196b37
--- /dev/null
+++ b/modules/blobtootel/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+  required_version = ">= 1.7.4"
+
+  required_providers {
+    azurerm = {
+      source  = "hashicorp/azurerm"
+      version = "~> 3.93"
+    }
+    random = {
+      source  = "hashicorp/random"
+      version = "~> 3.4.3"
+    }
+  }
+}


Reviews:
user: login: MichaelBriggs-Coralogix
body: Besides the two comments, everything looks good.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T12:49:40Z
user: login: MichaelBriggs-Coralogix
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T12:51:23Z
user: login: MichaelBriggs-Coralogix
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T12:52:16Z
user: login: MichaelBriggs-Coralogix
body: Looks good!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:05:57Z

Review comments:
diff_hunk: @@ -0,0 +1,134 @@
+locals {
+  function_name = join("-", ["BlobToOtel", substr(var.BlobContainerStorageAccount, 0, 27), random_string.this.result])
+  storage_name  = lower(substr(replace(join("", ["fn", var.BlobContainerStorageAccount, random_string.this.result]), "-", ""), 0, 24))
+  sku           = var.FunctionAppServicePlanType == "Consumption" ? "Y1" : "EP1"
+}
+
+resource "random_string" "this" {
+  length  = 5
+  special = false
+  lower   = true
+  upper   = false
+}
+
+# ------------------------------------------------ Blob Storage ------------------------------------------------
+
+data "azurerm_resource_group" "blobtootel-resourcegroup" {
+  name = var.BlobContainerResourceGroupName
+}
+
+data "azurerm_storage_account" "blobtootel-storageaccount" {
+  name                = var.BlobContainerStorageAccount
+  resource_group_name = var.BlobContainerResourceGroupName
+}
+
+# ------------------------------------------------ EventHub ------------------------------------------------
+
+data "azurerm_eventhub_namespace" "eventhub-namespace" {
+  name                = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub" "eventhub" {
+  name                = var.EventHubName
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub_namespace_authorization_rule" "eventhub-namespace-auth" {
+  name                = "RootManageSharedAccessKey" # This is the default rule name
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+resource "azurerm_eventhub_consumer_group" "eventhub-consumergroup" {
+  name                = local.function_name
+  namespace_name      = var.EventHubNamespace
+  eventhub_name       = var.EventHubName
+  resource_group_name = var.EventHubResourceGroup
+}
+
+# ------------------------------------------------ vNet Integration ------------------------------------------------
+
+# Add data source for subnet (only if Premium plan is selected)
+data "azurerm_subnet" "function_subnet" {
+  count                = var.FunctionAppServicePlanType == "Premium" ? 1 : 0
+  name                 = var.SubnetName
+  virtual_network_name = var.VirtualNetworkName
+  resource_group_name  = var.VirtualNetworkResourceGroup
+}
+
+# ------------------------------------------------ Function App ------------------------------------------------
+
+data "azurerm_resource_group" "functionRG" {
+  name = var.FunctionResourceGroupName
+}
+
+resource "azurerm_storage_account" "functionSA" {
+  name                     = local.storage_name
+  resource_group_name      = var.FunctionResourceGroupName
+  location                 = data.azurerm_resource_group.functionRG.location
+  account_tier             = "Standard"
+  account_replication_type = "LRS"
+}
+
+resource "azurerm_service_plan" "service-plan" {
+  name                = "${local.function_name}-plan"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  os_type             = "Linux"
+  sku_name            = local.sku
+}
+
+resource "azurerm_log_analytics_workspace" "crx-workspace" {
+  name                = "${local.function_name}-workspace"
+  location            = data.azurerm_resource_group.functionRG.location
+  resource_group_name = var.FunctionResourceGroupName
+  sku                 = "PerGB2018"
+  retention_in_days   = 90
+}
+
+resource "azurerm_application_insights" "crx-appinsights" {
+  name                = "${local.function_name}-appinsights"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  workspace_id        = azurerm_log_analytics_workspace.crx-workspace.id
+  application_type    = "web"
+}
+
+resource "azurerm_linux_function_app" "blobtootel-function" {
+  name                        = local.function_name
+  resource_group_name         = var.FunctionResourceGroupName
+  location                    = data.azurerm_resource_group.functionRG.location
+  storage_account_name        = azurerm_storage_account.functionSA.name
+  storage_account_access_key  = azurerm_storage_account.functionSA.primary_access_key
+  service_plan_id             = azurerm_service_plan.service-plan.id
+  functions_extension_version = "~4"
+  virtual_network_subnet_id   = var.FunctionAppServicePlanType == "Premium" ? data.azurerm_subnet.function_subnet[0].id : null
+  site_config {
+    application_insights_key               = azurerm_application_insights.crx-appinsights.instrumentation_key
+    application_insights_connection_string = azurerm_application_insights.crx-appinsights.connection_string
+    application_stack {
+      node_version = 20
+    }
+  }
+  app_settings = {
+    OTEL_EXPORTER_OTLP_ENDPOINT              = var.OtelEndpoint
+    CORALOGIX_DIRECT_MODE                    = var.CoralogixDirectMode
+    CORALOGIX_API_KEY                        = var.CoralogixApiKey
+    CORALOGIX_APPLICATION                    = var.CoralogixApplication
+    CORALOGIX_SUBSYSTEM                      = var.CoralogixSubsystem
+    EVENT_HUB_NAME                           = var.EventHubName
+    NEWLINE_PATTERN                          = var.NewlinePattern
+    PREFIX_FILTER                            = var.PrefixFilter
+    SUFFIX_FILTER                            = var.SuffixFilter
+    BLOB_STORAGE_ACCOUNT_CONNECTION_STRING   = data.azurerm_storage_account.blobtootel-storageaccount.primary_connection_string
+    EVENT_HUB_NAMESPACE_CONNECTION_STRING    = data.azurerm_eventhub_namespace_authorization_rule.eventhub-namespace-auth.primary_connection_string
+    AzureWebJobsStorage                      = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTSHARE                     = lower(local.function_name)
+    FUNCTIONS_EXTENSION_VERSION              = "~4"
+    FUNCTIONS_WORKER_RUNTIME                 = "node"
+    WEBSITE_RUN_FROM_PACKAGE                 = "https://coralogix-public.s3.eu-west-1.amazonaws.com/azure-functions-repo/BlobToOtel.zip"
+  }
+}
path: modules/blobtootel/main.tf
user: login: MichaelBriggs-Coralogix
body: Might be necessary to add similar "outputs" to your module to warn customers (These came from the StorageQueue module):

```
# ------------------------------------------------ Output ------------------------------------------------
output "RegionCheck" {
  value = data.azurerm_resource_group.functionRG.location == data.azurerm_resource_group.storagequeue-resourcegroup.location ? "[Info] Azure Function WAS deployed in the same region as the StorageQueue" : "[Notice] Azure Function WAS NOT deployed in the same region as the StorageQueue"
}

output "SyncTriggerCommand" {
  value = "Run this command to sync your StorageQueue Triggers:\n\taz resource invoke-action -g ${var.FunctionResourceGroupName} -n ${local.function_name} --action syncfunctiontriggers --resource-type Microsoft.Web/sites"
}
```
created_at: 2025-03-27T12:43:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,134 @@
+locals {
+  function_name = join("-", ["BlobToOtel", substr(var.BlobContainerStorageAccount, 0, 27), random_string.this.result])
+  storage_name  = lower(substr(replace(join("", ["fn", var.BlobContainerStorageAccount, random_string.this.result]), "-", ""), 0, 24))
+  sku           = var.FunctionAppServicePlanType == "Consumption" ? "Y1" : "EP1"
+}
+
+resource "random_string" "this" {
+  length  = 5
+  special = false
+  lower   = true
+  upper   = false
+}
+
+# ------------------------------------------------ Blob Storage ------------------------------------------------
+
+data "azurerm_resource_group" "blobtootel-resourcegroup" {
+  name = var.BlobContainerResourceGroupName
+}
+
+data "azurerm_storage_account" "blobtootel-storageaccount" {
+  name                = var.BlobContainerStorageAccount
+  resource_group_name = var.BlobContainerResourceGroupName
+}
+
+# ------------------------------------------------ EventHub ------------------------------------------------
+
+data "azurerm_eventhub_namespace" "eventhub-namespace" {
+  name                = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub" "eventhub" {
+  name                = var.EventHubName
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub_namespace_authorization_rule" "eventhub-namespace-auth" {
+  name                = "RootManageSharedAccessKey" # This is the default rule name
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+resource "azurerm_eventhub_consumer_group" "eventhub-consumergroup" {
+  name                = local.function_name
+  namespace_name      = var.EventHubNamespace
+  eventhub_name       = var.EventHubName
+  resource_group_name = var.EventHubResourceGroup
+}
+
+# ------------------------------------------------ vNet Integration ------------------------------------------------
+
+# Add data source for subnet (only if Premium plan is selected)
+data "azurerm_subnet" "function_subnet" {
+  count                = var.FunctionAppServicePlanType == "Premium" ? 1 : 0
+  name                 = var.SubnetName
+  virtual_network_name = var.VirtualNetworkName
+  resource_group_name  = var.VirtualNetworkResourceGroup
+}
+
+# ------------------------------------------------ Function App ------------------------------------------------
+
+data "azurerm_resource_group" "functionRG" {
+  name = var.FunctionResourceGroupName
+}
+
+resource "azurerm_storage_account" "functionSA" {
+  name                     = local.storage_name
+  resource_group_name      = var.FunctionResourceGroupName
+  location                 = data.azurerm_resource_group.functionRG.location
+  account_tier             = "Standard"
+  account_replication_type = "LRS"
+}
+
+resource "azurerm_service_plan" "service-plan" {
+  name                = "${local.function_name}-plan"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  os_type             = "Linux"
+  sku_name            = local.sku
+}
+
+resource "azurerm_log_analytics_workspace" "crx-workspace" {
+  name                = "${local.function_name}-workspace"
+  location            = data.azurerm_resource_group.functionRG.location
+  resource_group_name = var.FunctionResourceGroupName
+  sku                 = "PerGB2018"
+  retention_in_days   = 90
+}
+
+resource "azurerm_application_insights" "crx-appinsights" {
path: modules/blobtootel/main.tf
user: login: MichaelBriggs-Coralogix
body: I just got an email from Azure today saying something about application insights changing at the end of the month. Might be something we need to address. Here's a link to their migration doc:

https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-from-instrumentation-keys-to-connection-strings
created_at: 2025-03-27T12:48:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,134 @@
+locals {
+  function_name = join("-", ["BlobToOtel", substr(var.BlobContainerStorageAccount, 0, 27), random_string.this.result])
+  storage_name  = lower(substr(replace(join("", ["fn", var.BlobContainerStorageAccount, random_string.this.result]), "-", ""), 0, 24))
+  sku           = var.FunctionAppServicePlanType == "Consumption" ? "Y1" : "EP1"
+}
+
+resource "random_string" "this" {
+  length  = 5
+  special = false
+  lower   = true
+  upper   = false
+}
+
+# ------------------------------------------------ Blob Storage ------------------------------------------------
+
+data "azurerm_resource_group" "blobtootel-resourcegroup" {
+  name = var.BlobContainerResourceGroupName
+}
+
+data "azurerm_storage_account" "blobtootel-storageaccount" {
+  name                = var.BlobContainerStorageAccount
+  resource_group_name = var.BlobContainerResourceGroupName
+}
+
+# ------------------------------------------------ EventHub ------------------------------------------------
+
+data "azurerm_eventhub_namespace" "eventhub-namespace" {
+  name                = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub" "eventhub" {
+  name                = var.EventHubName
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub_namespace_authorization_rule" "eventhub-namespace-auth" {
+  name                = "RootManageSharedAccessKey" # This is the default rule name
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+resource "azurerm_eventhub_consumer_group" "eventhub-consumergroup" {
+  name                = local.function_name
+  namespace_name      = var.EventHubNamespace
+  eventhub_name       = var.EventHubName
+  resource_group_name = var.EventHubResourceGroup
+}
+
+# ------------------------------------------------ vNet Integration ------------------------------------------------
+
+# Add data source for subnet (only if Premium plan is selected)
+data "azurerm_subnet" "function_subnet" {
+  count                = var.FunctionAppServicePlanType == "Premium" ? 1 : 0
+  name                 = var.SubnetName
+  virtual_network_name = var.VirtualNetworkName
+  resource_group_name  = var.VirtualNetworkResourceGroup
+}
+
+# ------------------------------------------------ Function App ------------------------------------------------
+
+data "azurerm_resource_group" "functionRG" {
+  name = var.FunctionResourceGroupName
+}
+
+resource "azurerm_storage_account" "functionSA" {
+  name                     = local.storage_name
+  resource_group_name      = var.FunctionResourceGroupName
+  location                 = data.azurerm_resource_group.functionRG.location
+  account_tier             = "Standard"
+  account_replication_type = "LRS"
+}
+
+resource "azurerm_service_plan" "service-plan" {
+  name                = "${local.function_name}-plan"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  os_type             = "Linux"
+  sku_name            = local.sku
+}
+
+resource "azurerm_log_analytics_workspace" "crx-workspace" {
+  name                = "${local.function_name}-workspace"
+  location            = data.azurerm_resource_group.functionRG.location
+  resource_group_name = var.FunctionResourceGroupName
+  sku                 = "PerGB2018"
+  retention_in_days   = 90
+}
+
+resource "azurerm_application_insights" "crx-appinsights" {
+  name                = "${local.function_name}-appinsights"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  workspace_id        = azurerm_log_analytics_workspace.crx-workspace.id
+  application_type    = "web"
+}
+
+resource "azurerm_linux_function_app" "blobtootel-function" {
+  name                        = local.function_name
+  resource_group_name         = var.FunctionResourceGroupName
+  location                    = data.azurerm_resource_group.functionRG.location
+  storage_account_name        = azurerm_storage_account.functionSA.name
+  storage_account_access_key  = azurerm_storage_account.functionSA.primary_access_key
+  service_plan_id             = azurerm_service_plan.service-plan.id
+  functions_extension_version = "~4"
+  virtual_network_subnet_id   = var.FunctionAppServicePlanType == "Premium" ? data.azurerm_subnet.function_subnet[0].id : null
+  site_config {
+    application_insights_key               = azurerm_application_insights.crx-appinsights.instrumentation_key
+    application_insights_connection_string = azurerm_application_insights.crx-appinsights.connection_string
+    application_stack {
+      node_version = 20
+    }
+  }
+  app_settings = {
+    OTEL_EXPORTER_OTLP_ENDPOINT              = var.OtelEndpoint
+    CORALOGIX_DIRECT_MODE                    = var.CoralogixDirectMode
+    CORALOGIX_API_KEY                        = var.CoralogixApiKey
+    CORALOGIX_APPLICATION                    = var.CoralogixApplication
+    CORALOGIX_SUBSYSTEM                      = var.CoralogixSubsystem
+    EVENT_HUB_NAME                           = var.EventHubName
+    NEWLINE_PATTERN                          = var.NewlinePattern
+    PREFIX_FILTER                            = var.PrefixFilter
+    SUFFIX_FILTER                            = var.SuffixFilter
+    BLOB_STORAGE_ACCOUNT_CONNECTION_STRING   = data.azurerm_storage_account.blobtootel-storageaccount.primary_connection_string
+    EVENT_HUB_NAMESPACE_CONNECTION_STRING    = data.azurerm_eventhub_namespace_authorization_rule.eventhub-namespace-auth.primary_connection_string
+    AzureWebJobsStorage                      = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTSHARE                     = lower(local.function_name)
+    FUNCTIONS_EXTENSION_VERSION              = "~4"
+    FUNCTIONS_WORKER_RUNTIME                 = "node"
+    WEBSITE_RUN_FROM_PACKAGE                 = "https://coralogix-public.s3.eu-west-1.amazonaws.com/azure-functions-repo/BlobToOtel.zip"
+  }
+}
path: modules/blobtootel/main.tf
user: login: MichaelBriggs-Coralogix
body: The SyncTriggerCommand is just a way to address the required "refresh" of a function app deployed using zip files. 
created_at: 2025-03-27T12:51:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,134 @@
+locals {
+  function_name = join("-", ["BlobToOtel", substr(var.BlobContainerStorageAccount, 0, 27), random_string.this.result])
+  storage_name  = lower(substr(replace(join("", ["fn", var.BlobContainerStorageAccount, random_string.this.result]), "-", ""), 0, 24))
+  sku           = var.FunctionAppServicePlanType == "Consumption" ? "Y1" : "EP1"
+}
+
+resource "random_string" "this" {
+  length  = 5
+  special = false
+  lower   = true
+  upper   = false
+}
+
+# ------------------------------------------------ Blob Storage ------------------------------------------------
+
+data "azurerm_resource_group" "blobtootel-resourcegroup" {
+  name = var.BlobContainerResourceGroupName
+}
+
+data "azurerm_storage_account" "blobtootel-storageaccount" {
+  name                = var.BlobContainerStorageAccount
+  resource_group_name = var.BlobContainerResourceGroupName
+}
+
+# ------------------------------------------------ EventHub ------------------------------------------------
+
+data "azurerm_eventhub_namespace" "eventhub-namespace" {
+  name                = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub" "eventhub" {
+  name                = var.EventHubName
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+data "azurerm_eventhub_namespace_authorization_rule" "eventhub-namespace-auth" {
+  name                = "RootManageSharedAccessKey" # This is the default rule name
+  namespace_name      = var.EventHubNamespace
+  resource_group_name = var.EventHubResourceGroup
+}
+
+resource "azurerm_eventhub_consumer_group" "eventhub-consumergroup" {
+  name                = local.function_name
+  namespace_name      = var.EventHubNamespace
+  eventhub_name       = var.EventHubName
+  resource_group_name = var.EventHubResourceGroup
+}
+
+# ------------------------------------------------ vNet Integration ------------------------------------------------
+
+# Add data source for subnet (only if Premium plan is selected)
+data "azurerm_subnet" "function_subnet" {
+  count                = var.FunctionAppServicePlanType == "Premium" ? 1 : 0
+  name                 = var.SubnetName
+  virtual_network_name = var.VirtualNetworkName
+  resource_group_name  = var.VirtualNetworkResourceGroup
+}
+
+# ------------------------------------------------ Function App ------------------------------------------------
+
+data "azurerm_resource_group" "functionRG" {
+  name = var.FunctionResourceGroupName
+}
+
+resource "azurerm_storage_account" "functionSA" {
+  name                     = local.storage_name
+  resource_group_name      = var.FunctionResourceGroupName
+  location                 = data.azurerm_resource_group.functionRG.location
+  account_tier             = "Standard"
+  account_replication_type = "LRS"
+}
+
+resource "azurerm_service_plan" "service-plan" {
+  name                = "${local.function_name}-plan"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  os_type             = "Linux"
+  sku_name            = local.sku
+}
+
+resource "azurerm_log_analytics_workspace" "crx-workspace" {
+  name                = "${local.function_name}-workspace"
+  location            = data.azurerm_resource_group.functionRG.location
+  resource_group_name = var.FunctionResourceGroupName
+  sku                 = "PerGB2018"
+  retention_in_days   = 90
+}
+
+resource "azurerm_application_insights" "crx-appinsights" {
+  name                = "${local.function_name}-appinsights"
+  resource_group_name = var.FunctionResourceGroupName
+  location            = data.azurerm_resource_group.functionRG.location
+  workspace_id        = azurerm_log_analytics_workspace.crx-workspace.id
+  application_type    = "web"
+}
+
+resource "azurerm_linux_function_app" "blobtootel-function" {
+  name                        = local.function_name
+  resource_group_name         = var.FunctionResourceGroupName
+  location                    = data.azurerm_resource_group.functionRG.location
+  storage_account_name        = azurerm_storage_account.functionSA.name
+  storage_account_access_key  = azurerm_storage_account.functionSA.primary_access_key
+  service_plan_id             = azurerm_service_plan.service-plan.id
+  functions_extension_version = "~4"
+  virtual_network_subnet_id   = var.FunctionAppServicePlanType == "Premium" ? data.azurerm_subnet.function_subnet[0].id : null
+  site_config {
+    application_insights_key               = azurerm_application_insights.crx-appinsights.instrumentation_key
+    application_insights_connection_string = azurerm_application_insights.crx-appinsights.connection_string
+    application_stack {
+      node_version = 20
+    }
+  }
+  app_settings = {
+    OTEL_EXPORTER_OTLP_ENDPOINT              = var.OtelEndpoint
+    CORALOGIX_DIRECT_MODE                    = var.CoralogixDirectMode
+    CORALOGIX_API_KEY                        = var.CoralogixApiKey
+    CORALOGIX_APPLICATION                    = var.CoralogixApplication
+    CORALOGIX_SUBSYSTEM                      = var.CoralogixSubsystem
+    EVENT_HUB_NAME                           = var.EventHubName
+    NEWLINE_PATTERN                          = var.NewlinePattern
+    PREFIX_FILTER                            = var.PrefixFilter
+    SUFFIX_FILTER                            = var.SuffixFilter
+    BLOB_STORAGE_ACCOUNT_CONNECTION_STRING   = data.azurerm_storage_account.blobtootel-storageaccount.primary_connection_string
+    EVENT_HUB_NAMESPACE_CONNECTION_STRING    = data.azurerm_eventhub_namespace_authorization_rule.eventhub-namespace-auth.primary_connection_string
+    AzureWebJobsStorage                      = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = azurerm_storage_account.functionSA.primary_connection_string
+    WEBSITE_CONTENTSHARE                     = lower(local.function_name)
+    FUNCTIONS_EXTENSION_VERSION              = "~4"
+    FUNCTIONS_WORKER_RUNTIME                 = "node"
+    WEBSITE_RUN_FROM_PACKAGE                 = "https://coralogix-public.s3.eu-west-1.amazonaws.com/azure-functions-repo/BlobToOtel.zip"
+  }
+}
path: modules/blobtootel/main.tf
user: login: MichaelBriggs-Coralogix
body: The RegionCheck warning might not be relevant for your integration, but something to consider. I think it's more cost related, if the storage account and the functionapp are not in the same region, there are cross region access costs.
created_at: 2025-03-27T12:52:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: CLAassistant
created_at: 2025-03-27T09:56:20Z
body: [![CLA assistant check](https://cla-assistant.io/pull/badge/signed)](https://cla-assistant.io/coralogix/terraform-coralogix-azure?pullRequest=23) <br/>All committers have signed the CLA.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-27T14:07:04Z
body: :tada: This PR is included in version 1.0.11 :tada:

The release is available on [GitHub release](https://github.com/coralogix/terraform-coralogix-azure/releases/tag/v1.0.11)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE