A standalone Terraform module that provisions a single
azurerm_kusto_eventgrid_data_connection, ingesting Storage blobs into a Kusto (Azure Data Explorer) table via Event Grid notifications routed through an Event Hub. Targetshashicorp/azurerm ~> 4.0.
This module manages one Kusto Event Grid data connection and nothing else:
- π₯ Wires Storage blob events into a Kusto database table for continuous ingestion.
- π Routes storage notifications through an Event Hub consumer group so bursts are buffered reliably.
- π§Ύ Selects the blob event type (
BlobCreatedvsBlobRenamed), the source data format, and the mapping/table applied at ingest time. - πͺͺ Prefers a managed identity for the ingestion path instead of shared credentials.
- π§ Supports single- or multi-database routing, validated at parse time.
π‘ Why it matters: A Kusto cluster is only as useful as the data flowing into it. This module gives you a typed, secure-by-default ingestion edge that binds an existing Storage account, Event Hub, and Kusto database together, so a composition can stand up an event-driven analytics pipeline without hand-wiring the connection.
If this module saves you time, please consider supporting it:
- β Star the repo on GitHub β it helps others find the library.
- πΌ Connect on LinkedIn β linkedin.com/in/microsoftexpert
- β Buy me a coffee β buymeacoffee.com/microsoftexpert
flowchart LR
cluster["terraform-azurerm-kusto-cluster"]
db["terraform-azurerm-kusto-database"]
adc["terraform-azurerm-kusto-attached-database-configuration"]
script["terraform-azurerm-kusto-script"]
cosmos["terraform-azurerm-kusto-cosmosdb-data-connection"]
eg["terraform-azurerm-kusto-eventgrid-data-connection"]
eh["terraform-azurerm-kusto-eventhub-data-connection"]
iot["terraform-azurerm-kusto-iothub-data-connection"]
cmk["terraform-azurerm-kusto-cluster-customer-managed-key"]
mpe["terraform-azurerm-kusto-cluster-managed-private-endpoint"]
cpa["terraform-azurerm-kusto-cluster-principal-assignment"]
dpa["terraform-azurerm-kusto-database-principal-assignment"]
cluster -->|"hosts"| db
cluster -->|"attaches leader db"| adc
db -->|"target of"| script
db -->|"ingests via"| cosmos
db -->|"ingests via"| eg
db -->|"ingests via"| eh
db -->|"ingests via"| iot
cluster -->|"encrypted by, BY ID"| cmk
cluster -->|"private egress via, BY NAME"| mpe
cluster -->|"data-plane roles: ALL databases"| cpa
db -->|"data-plane roles: ONE database, prefer this"| dpa
mpe -->|"makes the source reachable"| eh
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef ext fill:#f2f2f2,stroke:#c8c8c8,color:#111111;
class eg me;
class cluster,db,adc,script,cosmos,eh,iot,cmk,mpe,cpa,dpa ext;
This module is one of the Kusto data-connection siblings (Cosmos DB / Event Grid / Event Hub / IoT Hub). It targets an existing azurerm_kusto_database and consumes an existing Storage account and Event Hub by id.
flowchart LR
in_id["name / location / resource_group_name / cluster_name / database_name"]
in_src["storage_account_id / eventhub_id / eventhub_consumer_group_name"]
in_opt["blob_storage_event_type / data_format / managed_identity_id"]
res["azurerm_kusto_eventgrid_data_connection.this"]
out_id["id"]
out_name["name"]
in_id -->|"input"| res
in_src -->|"input"| res
in_opt -->|"input"| res
res -->|"output"| out_id
res -->|"output"| out_name
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
class res me;
Resource inventory
| Resource | Cardinality | Role |
|---|---|---|
azurerm_kusto_eventgrid_data_connection.this |
single (keystone) | The Event Grid data connection bound to a Kusto database. |
The module owns a single resource. Its outputs go well beyond id and name, because several facts about this connection are invisible in state -- see the Outputs table.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | hashicorp/azurerm ~> 4.0 |
| Provider block | None in this module β the caller configures provider "azurerm" { features {} }, auth, subscription, and region. |
Schema notes that bite:
- π Force-new fields:
name,location,resource_group_name,cluster_name,database_name,storage_account_id,eventhub_id,eventhub_consumer_group_name, anddatabase_routing_typeall force replacement when changed. Treat the connection's identity and its source bindings as immutable. - π―
blob_storage_event_typeaccepts onlyMicrosoft.Storage.BlobCreatedorMicrosoft.Storage.BlobRenamed; anything else is rejected at parse time. - π§±
mapping_rule_nameandtable_name, when set, should already exist in the target database before the connection is created -- the connection references them and does not create them. Nothing verifies that the table or mapping exists -- notvalidate, notplan, notapply. Both fields carry only a FORMAT validator, and the provider never calls the Kusto validation API, so ARM stores whatever string you give it and returns success. A wrong name produces a connection that looks correct everywhere and ingests nothing. - π§Ύ
data_formatis validated against the Kusto-supported format set (APACHEAVRO,AVRO,CSV,JSON,MULTIJSON,ORC,PARQUET,PSV,RAW,SCSV,SINGLEJSON,SOHSV,TSV,TSVE,TXT,W3CLOGFILE). - πͺͺ
managed_identity_idtakes the cluster resource ID for a system-assigned identity, or the identity's resource ID for a user-assigned identity.
Least-privilege at the Kusto cluster/database scope:
Microsoft.Kusto/clusters/databases/dataConnections/writeMicrosoft.Kusto/clusters/databases/dataConnections/readMicrosoft.Kusto/clusters/databases/dataConnections/delete
The connection's identity additionally needs read on the Storage account and Event Hub. The built-in Contributor role on the cluster covers the connection operations.
- An existing Kusto cluster and database, a Storage account, and an Event Hub with a consumer group.
- An Event Grid subscription wiring the storage events to the Event Hub (supply its ID via
eventgrid_event_subscription_id, or let Azure create one). - The
Microsoft.Kustoresource provider registered on the subscription. - If
mapping_rule_nameortable_nameis set, the mapping and table must already exist in the target database.
terraform-azurerm-kusto-eventgrid-data-connection/
βββ providers.tf # required_version + azurerm ~> 4.0 pin; no provider block
βββ variables.tf # typed inputs; enum validations; timeouts tail (no tags)
βββ main.tf # keystone azurerm_kusto_eventgrid_data_connection.this
βββ outputs.tf # id (first), then name
βββ README.md # this document
βββ SCOPE.md # cross-module contract
βββ LICENSE # MIT
βββ .gitignore # canonical library ignore set
provider "azurerm" {
features {}
}
module "kusto_eventgrid_ingest" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "blob-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-analytics/providers/Microsoft.Storage/storageAccounts/adxlanding"
eventhub_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-analytics/providers/Microsoft.EventHub/namespaces/adx-ns/eventhubs/blob-events"
eventhub_consumer_group_name = "adx-ingest"
}βΉοΈ The caller owns the provider, its authentication, subscription/region selection, and the mandatory
features {}block. This module never declares aprovider "azurerm"block.
Consumes
| Input | Type | Source module |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group |
cluster_name / database_name |
string |
terraform-azurerm-kusto-cluster / terraform-azurerm-kusto-database |
storage_account_id |
string |
terraform-azurerm-storage-account |
eventhub_id |
string |
terraform-azurerm-eventhub-namespace (an event hub) |
managed_identity_id |
string |
terraform-azurerm-user-assigned-identity (optional) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Resource ID of the data connection | references |
name |
Connection name | references |
1 Β· Minimal blob-created ingestion
The smallest real call β every required binding, provider defaults for everything optional.
module "ingest_min" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "blob-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
}π‘ With no
blob_storage_event_typeset, the connection uses the provider default (Microsoft.Storage.BlobCreated).
2 Β· Explicit BlobCreated events
Pin the event type to newly created blobs for a write-once landing zone.
module "ingest_created" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "created-ingest"
location = "eastus2"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
blob_storage_event_type = "Microsoft.Storage.BlobCreated"
}βΉοΈ
BlobCreatedfires on new-blob writes β the common pattern for append-only ingestion.
3 Β· BlobRenamed events (ADLS Gen2 rename)
Ingest on the rename event, useful when a writer stages a file and atomically renames it into place on a hierarchical-namespace account.
module "ingest_renamed" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "renamed-ingest"
location = "westus2"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
blob_storage_event_type = "Microsoft.Storage.BlobRenamed"
}
β οΈ BlobRenamedrequires an ADLS Gen2 (hierarchical namespace) account; block-blob-only accounts do not emit it.
4 Β· JSON data format with a mapping rule
Bind a pre-created JSON mapping and target table.
module "ingest_json" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "json-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
data_format = "JSON"
mapping_rule_name = "events_json_mapping"
table_name = "RawEvents"
}π
mapping_rule_nameandtable_namemust already exist in the database β create them with a Kusto script before this connection.
5 Β· MULTIJSON for line-delimited payloads
Use MULTIJSON when each blob holds multiple JSON objects.
module "ingest_multijson" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "multijson-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
data_format = "MULTIJSON"
table_name = "RawEvents"
}π‘
MULTIJSONparses a stream of JSON objects;JSONexpects one object per line.
6 Β· CSV with skip_first_record for headers
Skip the header row of every CSV file.
module "ingest_csv" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "csv-ingest"
location = "centralus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
data_format = "CSV"
skip_first_record = true
table_name = "SalesCsv"
}βΉοΈ
skip_first_recordleft null uses the provider default (false).
7 Β· PARQUET columnar ingestion
Point the connection at Parquet blobs.
module "ingest_parquet" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "parquet-ingest"
location = "eastus2"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
data_format = "PARQUET"
mapping_rule_name = "events_parquet_mapping"
table_name = "RawEvents"
}π‘ Columnar formats (
PARQUET,ORC,AVRO) usually pair with a mapping rule that names the source columns.
8 Β· System-assigned managed identity
Use the cluster's system-assigned identity for the ingestion path β supply the cluster resource ID.
module "ingest_sysid" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "sysid-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
managed_identity_id = azurerm_kusto_cluster.adx.id
}π Prefer managed identity over shared keys β the cluster identity needs read on the Storage account and Event Hub.
9 Β· User-assigned managed identity
Use a dedicated user-assigned identity β supply the identity's resource ID.
module "ingest_uami" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "uami-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
managed_identity_id = azurerm_user_assigned_identity.ingest.id
}βΉοΈ The user-assigned identity must be attached to the cluster and granted read on the source resources.
10 Β· Multi-database routing
Allow the connection to route to a database named in the ingestion payload rather than only the bound database.
module "ingest_multi" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "multi-route-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
database_routing_type = "Multi"
}
β οΈ database_routing_typeis force-new β flipping betweenSingleandMultireplaces the connection.
11 Β· Explicit Event Grid subscription ID
Reference an Event Grid subscription you manage separately instead of letting Azure create one.
module "ingest_egsub" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "egsub-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
eventgrid_event_subscription_id = azurerm_eventgrid_event_subscription.blobs.id
}π‘ Supplying the subscription ID keeps the Event Grid wiring in your own state rather than as a side effect.
12 Β· Custom timeouts
Extend the create/delete windows for a slow control-plane region.
module "ingest_timeouts" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "timeout-ingest"
location = "westus2"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
timeouts = {
create = "60m"
delete = "60m"
}
}βΉοΈ Timeouts are Go duration strings; unset operations fall back to the provider defaults.
13 Β· Fully specified connection
Every knob set β event type, format, mapping, table, routing, identity, and skip.
module "ingest_full" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "full-ingest"
location = "eastus"
resource_group_name = "rg-analytics"
cluster_name = "adx-prod"
database_name = "telemetry"
storage_account_id = azurerm_storage_account.landing.id
eventhub_id = azurerm_eventhub.blob_events.id
eventhub_consumer_group_name = "adx-ingest"
blob_storage_event_type = "Microsoft.Storage.BlobCreated"
data_format = "JSON"
database_routing_type = "Single"
eventgrid_event_subscription_id = azurerm_eventgrid_event_subscription.blobs.id
managed_identity_id = azurerm_user_assigned_identity.ingest.id
mapping_rule_name = "events_json_mapping"
table_name = "RawEvents"
skip_first_record = false
}π A fully declarative connection keeps the ingestion contract in code and out of the Kusto portal.
14 Β· ποΈ End-to-end composition
Wire sibling module outputs into this module β resource group, storage account, Event Hub namespace, user-assigned identity, Kusto cluster, and Kusto database all feed the connection.
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-analytics"
location = "eastus"
}
module "identity" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-user-assigned-identity.git?ref=v1.0.0"
name = "id-adx-ingest"
location = module.rg.location
resource_group_name = module.rg.name
}
module "storage" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-storage-account.git?ref=v1.0.0"
name = "adxlanding001"
location = module.rg.location
resource_group_name = module.rg.name
}
module "eventhub" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-eventhub-namespace.git?ref=v1.0.0"
name = "adx-ns"
location = module.rg.location
resource_group_name = module.rg.name
# Quote any key containing a hyphen: HCL reads a bare blob-events as the
# expression `blob - events`, which is a parse error, not a plan error.
event_hubs = {
"blob-events" = {
name = "blob-events"
partition_count = 4
message_retention = 1
}
}
# consumer_groups is a SEPARATE top-level variable, not an attribute of an
# event_hubs entry -- nested there it is silently discarded and no consumer
# group is created, while the connection below still names one.
consumer_groups = {
"adx-ingest" = {
name = "adx-ingest"
eventhub_key = "blob-events"
}
}
}
module "kusto_cluster" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-cluster.git?ref=v1.0.0"
sku = {
name = "Standard_E8ads_v5"
capacity = 2
}
name = "adxprod001"
location = module.rg.location
resource_group_name = module.rg.name
}
module "kusto_db" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-database.git?ref=v1.0.0"
name = "telemetry"
location = module.rg.location
resource_group_name = module.rg.name
cluster_name = module.kusto_cluster.name
}
module "eventgrid_ingest" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-kusto-eventgrid-data-connection.git?ref=v1.0.0"
name = "blob-ingest"
location = module.rg.location
resource_group_name = module.rg.name
cluster_name = module.kusto_cluster.name
database_name = module.kusto_db.name
storage_account_id = module.storage.id
eventhub_id = module.eventhub.eventhub_ids["blob-events"]
eventhub_consumer_group_name = "adx-ingest"
blob_storage_event_type = "Microsoft.Storage.BlobCreated"
data_format = "JSON"
managed_identity_id = module.identity.id
mapping_rule_name = "events_json_mapping"
table_name = "RawEvents"
skip_first_record = false
}βΉοΈ Output names shown for sibling modules (
eventhub_ids,id,name,location) follow this suite's conventions; align them with the exact outputs of the versions you pin. The mapping rule and table should be created in the database (for example via a Kusto script) before this connection -- nothing enforces the ordering.
Required β the connection identity and its source bindings.
| Name | Type | Description |
|---|---|---|
name |
string |
Connection name. Force-new. |
location |
string |
Azure region of the Kusto database. Force-new. |
resource_group_name |
string |
Resource group of the cluster/database. Force-new. |
cluster_name |
string |
Kusto cluster name. Force-new. |
database_name |
string |
Kusto database name. Force-new. |
storage_account_id |
string |
Storage account resource ID whose blob events drive ingestion. Force-new. |
eventhub_id |
string |
Event Hub resource ID carrying the storage events. Force-new. |
eventhub_consumer_group_name |
string |
Event Hub consumer group used for ingestion. Force-new. |
Optional β ingestion shaping, identity, and the timeouts tail.
| Name | Type | Default | Description |
|---|---|---|---|
blob_storage_event_type |
string |
null |
Microsoft.Storage.BlobCreated or Microsoft.Storage.BlobRenamed. |
data_format |
string |
null |
Source data format (validated enum). |
database_routing_type |
string |
null |
Single or Multi. Force-new. |
eventgrid_event_subscription_id |
string |
null |
Event Grid subscription resource ID. |
managed_identity_id |
string |
null |
Managed identity resource ID for ingestion. |
mapping_rule_name |
string |
null |
Existing mapping rule applied at ingest. |
table_name |
string |
null |
Existing target table. |
skip_first_record |
bool |
null |
Ignore the first record of every file. |
timeouts |
object(...) |
null |
Per-operation timeouts. |
Full input schemas
variable "blob_storage_event_type" {
type = string
default = null
# "Microsoft.Storage.BlobCreated" | "Microsoft.Storage.BlobRenamed"
}
variable "data_format" {
type = string
default = null
# APACHEAVRO | AVRO | CSV | JSON | MULTIJSON | ORC | PARQUET | PSV |
# RAW | SCSV | SINGLEJSON | SOHSV | TSV | TSVE | TXT | W3CLOGFILE
}
variable "database_routing_type" {
type = string
default = null
# "Single" | "Multi" β force-new
}
variable "skip_first_record" {
type = bool
default = null
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}βΉοΈ This resource does not support
tags, so the universal tail istimeoutsonly.
| Output | Description | Kind |
|---|---|---|
id |
Passthrough | |
name |
Name of the data connection, unique within the target Kusto database | Passthrough |
cluster_name |
Name of the Kusto cluster hosting this connection | Passthrough |
database_name |
Name of the Kusto database this connection ingests into by default | Passthrough |
resource_group_name |
Resource group containing the parent Kusto cluster | Passthrough |
location |
Azure region of the connection, normalised by the provider ("East US" is stored as "eastus") | Passthrough |
storage_account_id |
Resource ID of the Storage account whose blob events drive ingestion and from which Kusto fetches blob content | Passthrough |
eventhub_id |
Resource ID of the Event Hub carrying the Event Grid notifications | Passthrough |
eventhub_consumer_group_name |
Event Hub consumer group this connection reads from | Passthrough |
eventgrid_event_subscription_id |
Passthrough | |
blob_storage_event_type |
"Microsoft.Storage.BlobCreated" or "Microsoft.Storage.BlobRenamed" | Passthrough |
data_format |
Static data format applied to blobs that carry no format metadata of their own | Passthrough |
database_routing_type |
Passthrough | |
skip_first_record |
Whether the first record of every blob is discarded | Passthrough |
table_name |
Static target table for blobs carrying no table metadata | Passthrough |
mapping_rule_name |
Static ingestion mapping for blobs carrying no mapping metadata | Passthrough |
managed_identity_id |
Passthrough | |
uses_managed_identity |
Whether an explicit managed identity was supplied for the ingestion path | Passthrough |
target_table_and_mapping_are_invisible_to_terraform |
Constant | |
in_place_update_applies_every_non_force_new_field |
Constant | |
managed_identity_cannot_be_cleared_in_place |
Constant | |
malformed_records_can_be_dropped_without_error |
Constant | |
read_uses_the_delete_timeout |
Constant | |
plan_does_not_require_credential_access |
Constant | |
destroy_stops_ingestion_and_deletes_no_data |
Constant | |
provider_default_timeouts |
The provider's built-in timeouts for this resource, for reference when deciding whether to override them: create 60m, update 60m, delete 60m, read 5m | Derived |
- Immutable core. The connection's identity (
name), its location and resource group, the cluster and database it targets, the storage/event-hub bindings, anddatabase_routing_typeare all force-new. Any change to these replaces the connection, which briefly interrupts ingestion β plan such edits as create-before-destroy where your workflow allows. - The Event Grid β Event Hub β Kusto flow. Blob write/rename events are published to Event Grid, delivered to the Event Hub, and read by Kusto through the named consumer group. This module owns only the Kusto-side connection; the storage account, Event Hub, and (optionally) the Event Grid subscription are referenced by id and created by sibling modules.
- Enum validations fail fast.
blob_storage_event_type,data_format, anddatabase_routing_typeeach carry avalidation {}block, so a malformed value is a plan-time type error rather than an Azure API rejection mid-apply. Anullvalue defers to the provider default in every case. - Mapping and table are references, not resources. When
mapping_rule_nameortable_nameis set, those objects must already exist in the database; the connection binds to them. features {}dependence. Like every module in this library, this one declares noproviderblock. It will not initialize in isolation without a caller-suppliedprovider "azurerm" { features {} }β that is expected, not a module defect.
| Principle | How this module applies it |
|---|---|
| Enums validated at parse time | blob_storage_event_type, data_format, and database_routing_type are checked with validation {} blocks; illegal values never reach Azure. |
| Identity over secrets | managed_identity_id is the intended ingestion credential path β a system- or user-assigned managed identity, not a shared key. |
| Least-privilege scope | The RBAC section names only the dataConnections verbs at the cluster/database scope. |
| No tags | The resource does not support tags; the universal tail is timeouts only, and no tags variable is offered. |
| Safe by omission | Every optional field defaults to null, deferring to the provider's own (safe) default rather than inventing one. |
# Offline validation β no backend, no cloud calls
terraform init -backend=false
terraform validate
terraform fmt -check
# Plan against a real subscription (a human applies from CI)
terraform plan- Pin the module with
?ref=v1.0.0β never a branch. - This library is plan-only;
terraform applyruns from CI under human review, never from this authoring flow. - Clean up
.terraform/after offline validation.
| Gate | Covers | Does not cover |
|---|---|---|
terraform plan |
Type correctness of every input, enum validation {} blocks, reference integrity β offline, without credentials. (terraform validate alone reaches the types but not the validation {} blocks, which it never evaluates through a module call.) |
Whether the cluster, database, storage account, or Event Hub actually exist. |
terraform fmt -check |
Canonical formatting. | Semantics. |
terraform plan |
The full resource graph against the live provider, and force-new detection. Not mapping or table existence -- the API surfaces nothing of the kind. | Runtime ingestion -- only an apply plus real blob events proves the data path. |
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-analytics/providers/Microsoft.Kusto/clusters/adx-prod/databases/telemetry/dataConnections/blob-ingest"
name = "blob-ingest"
| Symptom | Cause | Fix |
|---|---|---|
blob_storage_event_type must be ... at plan |
A value outside the two allowed event types. | Use Microsoft.Storage.BlobCreated or Microsoft.Storage.BlobRenamed, or leave it null. |
data_format must be one of ... at plan |
An unsupported format string. | Use one of the validated Kusto formats (e.g. JSON, CSV, PARQUET). |
| Ingestion is silent and no rows land | mapping_rule_name / table_name name an object that does not exist. The apply did NOT fail -- nothing validates them, so ARM accepted the strings and returned success. |
Create the table and mapping in the database first (for example via a Kusto script). Unlike the Event Hub sibling, this resource's update path does apply a corrected name in place. |
| Plan shows the connection being replaced | A force-new field changed (name, location, RG, cluster, database, storage/event-hub binding, or routing type). | Confirm the change is intended; sequence a create-before-destroy if ingestion continuity matters. |
provider ... features initialization error |
The caller's root module is missing provider "azurerm" { features {} }. |
Add the features {} block to the caller's provider configuration. |
| No data arrives despite a healthy connection | Event Grid subscription or consumer group not wired to this Event Hub, or the identity lacks read on the source. | Verify the Event Grid subscription targets the Event Hub and grant the ingestion identity read on the storage account and Event Hub. |
| Access denied during ingestion | The managed identity lacks read on the Storage account or Event Hub. | Grant the identity in managed_identity_id read at the source scopes. |
- Terraform Registry β
azurerm_kusto_eventgrid_data_connection - Terraform Registry β
azurerm_kusto_clusterΒ·azurerm_kusto_database - Sibling modules β
terraform-azurerm-kusto-cluster,terraform-azurerm-kusto-database,terraform-azurerm-kusto-eventhub-data-connection,terraform-azurerm-kusto-cosmosdb-data-connection,terraform-azurerm-kusto-iothub-data-connection,terraform-azurerm-kusto-script,terraform-azurerm-kusto-attached-database-configuration - This module's cross-module contract β
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."