Registers one self-hosted integration runtime on a Synapse workspace — the registration for compute you run yourself, to reach data Azure cannot. Targets
hashicorp/azurerm ~> 4.0.
- 🧩 Creates one
azurerm_synapse_integration_runtime_self_hosted— a registration for a runtime you install and operate. - 🔴 Leads with the fact that decides who may run a plan: the provider reads this runtime's two authorization keys on every refresh. Plan access is key access.
- 🔓 States that the provider marks neither key sensitive — both are plain Computed strings, so anything that prints them prints them in the clear.
- 🔐 Never emits either key. SHA-256 fingerprints and a distinctness flag give the rotation-comparison power with none of the exposure.
- 🖥️ Explains that this record runs nothing until a node is installed on a machine and registered with one of those keys — and that Terraform cannot see whether one is.
- 🕳️ Documents a failure mode with no obvious cause: a not-found on the key listing drops the runtime from state, even though the runtime itself was found.
- 🚩 Reports a defect in the provider's own name message — it claims a three-character minimum its regular expression does not enforce, the mirror image of the Azure sibling's.
- 🏷️ Carries no
tagsand nolocation— the resource has neither. The universal tail istimeoutsonly.
💡 Why it matters: this is a three-argument resource that looks trivial and is the most credential-exposed one in the Synapse family. Its keys are read on every plan, redacted by nothing, and written to state in plaintext whether or not anything uses them — and the runtime they protect is the one with a route into your on-premises network.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart TB
rg["terraform-azurerm-resource-group"]
ws["terraform-azurerm-synapse-workspace"]
this["terraform-azurerm-synapse-integration-runtime-self-hosted"]
azir["terraform-azurerm-synapse-integration-runtime-azure"]
node["a machine you own, running the runtime software"]
src["a data source Azure cannot reach"]
rg -->|"name"| ws
ws -->|"id"| this
ws -->|"id"| azir
this -->|"an authorization key, carried to the node by hand"| node
node -->|"reads"| src
azir -->|"the other runtime type, Microsoft-hosted compute"| ws
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef keystone fill:#004578,stroke:#002b4d,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b9c7d6,color:#1b2a3a;
class this me;
class ws keystone;
class rg,azir,node,src sib;
The two nodes on the right are the point of the resource and neither is in Azure. A machine you own runs the runtime software, registers with an authorization key carried to it out of band, and reaches a data source that Azure has no route to. Nothing in Terraform can see either of them.
flowchart TB
subgraph inputs["Inputs"]
ident["name plus synapse_workspace_id, both force-new"]
desc["description, the only field that updates in place"]
end
this["azurerm_synapse_integration_runtime_self_hosted.this"]
subgraph keys["Computed by Azure, read on every refresh"]
k1["authorization_key_primary"]
k2["authorization_key_secondary"]
end
subgraph outputs["Outputs"]
oid["id, name, synapse_workspace_id, synapse_workspace_name, resource_group_name"]
ofp["authorization_key_primary_fingerprint, authorization_key_secondary_fingerprint, authorization_keys_are_distinct"]
osec["reading_this_resource_reads_its_credentials, the_provider_does_not_redact_these_keys, the_keys_are_in_state_in_plaintext"]
ofact["this_registration_runs_nothing_until_a_node_is_installed, a_missing_key_response_removes_this_from_state, name_is_shorter_than_three_characters"]
end
ident --> this
desc --> this
this --> k1
this --> k2
k1 -->|"hashed, never emitted"| ofp
k2 -->|"hashed, never emitted"| ofp
this --> oid
this --> osec
this --> ofact
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b9c7d6,color:#1b2a3a;
class this me;
class ident,desc,k1,k2,oid,ofp,osec,ofact sib;
Resource inventory
| Resource | Count | Notes |
|---|---|---|
azurerm_synapse_integration_runtime_self_hosted |
1 | The keystone this. No children — the nodes are not Azure resources. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module — the caller configures the provider, its authentication, and the mandatory features {} block. |
Schema notes that bite
- 🔴 The read path calls a second client and lists the authorization keys on every refresh. That is what
makes plan access credential access, and it is why
readcovers two API calls inside one timeout. - 🔴 Neither authorization key is marked
Sensitive. Both are plain Computed strings, so nothing redacts them anywhere by default. - 🔴 If the key-listing call returns not-found, the provider clears the resource ID and drops the record from state even though the runtime itself was found. The next plan proposes a create, which then fails against the existing runtime.
- 🔴 The provider's name error message claims a three-character minimum its regular expression does not enforce — a one-character name is accepted. The consecutive-dash rule the same message claims is enforced here.
- 🔴 The Azure sibling carries the same message and a different pattern, wrong about the opposite half.
- Two force-new fields, both declared on the resource itself:
nameandsynapse_workspace_id. Onlydescriptionupdates in place — and a replacement issues new keys, so every node must be re-registered. - There is no
locationand no compute size. The machine you install the runtime on is the compute. - This resource does have an existence check on create — an apply over an existing runtime fails and tells you to import.
- No
tags.
Least-privilege, at the smallest scope that works:
Microsoft.Synapse/workspaces/integrationRuntimes/writeand.../integrationRuntimes/deleteon the target workspace. A custom role scoped to the workspace is enough; Contributor on the workspace, or on its resource group, covers this more broadly than necessary.Microsoft.Synapse/workspaces/integrationRuntimes/readon the workspace, for the existence check before creating and for every refresh.- 🔴
Microsoft.Synapse/workspaces/integrationRuntimes/listAuthKeys/actionon the workspace — required by every refresh, not only by an explicit key read.
🔴 Plan access is credential access on this resource. A
terraform planreads this runtime's two registration keys. Grant refresh rights only to identities you would hand those keys to — and note that the provider marks neither key sensitive, so they are not redacted anywhere by default.
⚠️ The key's own blast radius is not bounded by any Azure role. An authorization key registers a new node against this runtime, and a registered node executes pipeline activities with whatever access those activities have — including to the on-premises systems the runtime exists to reach.
- An existing Synapse workspace (the
synapse-workspacemodule). - One or more machines you control to install the runtime software on, with outbound connectivity to Azure and a route to whatever data the runtime is meant to reach.
- A way to get an authorization key to those machines out of band. This module fingerprints the keys and never emits them.
- A pipeline or linked service that selects this runtime by name, if it is to do anything.
- The caller configures the
provider "azurerm" { features {} }block, auth, and subscription.
terraform-azurerm-synapse-integration-runtime-self-hosted/
├── providers.tf # required_version and the pinned azurerm; no provider block
├── variables.tf # the workspace, the name, the description, timeouts
├── main.tf # the single keystone azurerm_synapse_integration_runtime_self_hosted.this
├── outputs.tf # id first, then identity, then key fingerprints and the quiet facts
├── README.md # this file
├── SCOPE.md # the cross-module contract
├── LICENSE # MIT
└── .gitignore
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = var.synapse_workspace_id
}
⚠️ The caller configuresprovider "azurerm" { features {} }, its authentication, and its subscription. This module declares no provider block.
🚨 This registration runs nothing. Install the runtime software on a machine and register it with one of the authorization keys, or the runtime exists, looks healthy in Studio, and executes nothing. See example 2.
Consumes
| Input | Type | Source |
|---|---|---|
synapse_workspace_id |
string |
terraform-azurerm-synapse-workspace output id |
Emits
| Output | Consumed by |
|---|---|
id |
management locks, role assignments, review |
name |
the pipeline activity that selects this runtime, and the node that reports against it |
authorization_key_primary_fingerprint |
rotation review |
reading_this_resource_reads_its_credentials |
permissions review |
this_registration_runs_nothing_until_a_node_is_installed |
design review |
1 · The smallest real call
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = var.synapse_workspace_id
}💡 The resource takes three arguments and only
descriptionis optional. There is no region and no compute size — the machine you install the runtime on is the compute.
2 · What this registration does not do
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = var.synapse_workspace_id
}
output "still_needs_a_node" {
value = module.onprem_runtime.this_registration_runs_nothing_until_a_node_is_installed
}🚨 A self-hosted runtime is a registration, not compute. Until the runtime software is installed on a machine and registered with one of the authorization keys, this record exists, appears in Synapse Studio, and executes nothing. An apply proves only that the registration was created.
⚠️ Nothing in Terraform can see whether a node is attached, how many there are, or whether they are healthy. That is what thedescriptionfield is for — see example 6.
3 · Why plan access is key access
output "a_plan_reads_the_registration_keys" {
value = module.onprem_runtime.reading_this_resource_reads_its_credentials
}🔴 The provider's read path calls a second client and lists this runtime's two authorization keys on every refresh — not only when something references them. So a
terraform planis a credential read, and the identity running it needsMicrosoft.Synapse/workspaces/integrationRuntimes/listAuthKeys/action.
⚠️ Grant refresh rights on this resource only to identities you would hand the registration keys to. An authorization key registers a new node, and a registered node runs pipeline activities with whatever access those activities have — including into the network the runtime exists to reach.
4 · The keys, and why this module will not give them to you
output "primary_key_fingerprint" {
value = module.onprem_runtime.authorization_key_primary_fingerprint
}
output "keys_differ" {
value = module.onprem_runtime.authorization_keys_are_distinct
}
output "the_provider_redacts_nothing" {
value = module.onprem_runtime.the_provider_does_not_redact_these_keys
}🔒 Neither key is emitted, in any form but a SHA-256 hash. The provider marks neither of them
Sensitive, which makes re-emitting worse than usual: the value would be copied into every consuming configuration's state and printed in the clear in any plan that shows it.
🚨
sensitive = truewould redact plan output and would NOT encrypt state. These keys are read into state on every refresh whether or not anything uses them, so the state file holds live registration credentials. The control that matters is an encrypted, access-controlled backend that is never a local file in a repository.
ℹ️ To register a node, read the key from Azure at the moment you need it — the portal or the CLI — rather than routing it through Terraform outputs.
5 · Rotating a key without printing one
output "key_fingerprints" {
value = {
primary = module.onprem_runtime.authorization_key_primary_fingerprint
secondary = module.onprem_runtime.authorization_key_secondary_fingerprint
distinct = module.onprem_runtime.authorization_keys_are_distinct
}
}💡 Two keys exist so that one can be rotated while nodes are still registering with the other: point new registrations at the secondary, regenerate the primary, then swap back. Comparing the fingerprints across runs is how you confirm that sequence actually happened — without either key appearing in a log.
⚠️ Adistinct = falseis worth investigating rather than ignoring. Two identical keys defeat the point of having two.
6 · Recording what Terraform cannot see
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = var.synapse_workspace_id
description = "Nodes: dc1-etl01 and dc1-etl02, owned by the on-premises platform team. Reaches the finance SQL Server cluster. Key rotation: quarterly, secondary first."
}💡
descriptionis the only place on this record to write down which machines are registered against it and who owns them. The nodes are not Azure resources, so nothing in the portal, the state file or this module can tell you what is attached — and there is notagsargument here either.
7 · The failure mode with no obvious cause
output "why_it_vanished_from_state" {
value = module.onprem_runtime.a_missing_key_response_removes_this_from_state
}🕳️ The provider's read fetches the runtime and then lists its authorization keys. If that second call returns not-found, the provider clears the resource ID and drops the record from state — even though the runtime itself was found a moment earlier.
🚨 The next plan then proposes to create it, and that create fails against the existing runtime, because this resource does have an existence check. Re-import rather than fighting the plan:
terraform import 'module.onprem_runtime.azurerm_synapse_integration_runtime_self_hosted.this' \ '/subscriptions/SUB/resourceGroups/RG/providers/Microsoft.Synapse/workspaces/WORKSPACE/integrationRuntimes/ir-onprem-sql'
8 · The name rule the provider misdocuments
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
# ACCEPTED here, even though the provider's own error message claims a
# three-character minimum -- its regular expression does not enforce one.
name = "i"
synapse_workspace_id = var.synapse_workspace_id
}
output "relying_on_a_provider_gap" {
value = module.onprem_runtime.name_is_shorter_than_three_characters
}🚩 This module mirrors the provider's pattern, not its message. A rule the provider does not enforce may be one Azure does not enforce either, and a
validation {}failure would blockterraform destroyas well as apply — so the condition is reported rather than refused.
⚠️ Atruehere means you are relying on that gap. A one-character runtime name is also a poor thing to ask an operator to type into a node registration dialog.
9 · Two runtimes, two different name rules
# THIS resource: consecutive dashes REJECTED, one-character name ACCEPTED.
# "ir--onprem" rejected
# "i" accepted
# The AZURE runtime: minimum 3 characters ENFORCED, consecutive dashes PERMITTED.
# "ir--dataflow" accepted
# "ab" rejected
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql" # legal on both
synapse_workspace_id = var.synapse_workspace_id
}
output "do_not_assume_one_convention" {
value = module.onprem_runtime.the_two_runtime_types_do_not_share_a_name_rule
}ℹ️ Both resources carry the same error message text and different regular expressions, and each message is wrong about the opposite half. A name of at least three characters using only single dashes is legal on both, which is the convention worth adopting.
10 · What a replacement costs you
output "what_replaces_this_runtime" {
value = module.onprem_runtime.force_new_fields
}# Both force-new fields are ordinary-looking:
# name -- and pipelines name the runtime by this string
# synapse_workspace_id -- a runtime belongs to one workspace
#
# Only `description` updates in place.🚨 A replacement issues new authorization keys. Every machine registered against the old runtime stops working and has to be re-registered by hand, on each node, with a key someone has to fetch and carry. Renaming this resource is an operations task, not a Terraform edit.
11 · This is not the Azure runtime
# THIS module -> azurerm_synapse_integration_runtime_self_hosted
# A REGISTRATION for compute you run yourself. No location, no core count,
# two authorization keys the provider reads on every refresh.
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = var.synapse_workspace_id
}
# The SIBLING -> azurerm_synapse_integration_runtime_azure
# Microsoft-hosted compute, with a region and a core count. No credentials.
module "dataflow_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-azure.git?ref=v1.0.0"
name = "ir-dataflow-prod"
synapse_workspace_id = var.synapse_workspace_id
location = "eastus2"
}🔒 The difference that matters operationally: plan access on this resource is credential access, and on the Azure one it is not. Reach for this module only when the data is somewhere Azure cannot reach — a self-hosted runtime is a machine to patch, monitor and re-register, and the managed one is not.
12 · Several runtimes, one per site
locals {
sites = {
"ir-onprem-london" = "Nodes: lon-etl01, lon-etl02. Reaches the London finance SQL cluster."
"ir-onprem-frankfurt" = "Nodes: fra-etl01. Reaches the Frankfurt warehouse."
}
}
module "site_runtimes" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
for_each = local.sites
name = each.key
synapse_workspace_id = var.synapse_workspace_id
description = each.value
}
output "key_fingerprints_by_site" {
value = { for k, m in module.site_runtimes : k => m.authorization_key_primary_fingerprint }
}💡 One runtime per site rather than one shared runtime keeps each site's nodes registering with their own keys, so a key rotation at one site does not touch another. The fingerprint map is how you confirm a rotation landed where it was meant to.
⚠️ The map key becomes thename, which is force-new — and a replacement issues new keys for that site. Treat these keys as an interface, not as labels.
13 · 🏗️ End-to-end composition
module "data_rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-data-platform"
location = "eastus2"
tags = {
environment = "prod"
owner = "data-platform-team"
}
}
module "synapse" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-workspace.git?ref=v1.0.0"
name = "syn-platform-prod"
resource_group_name = module.data_rg.name
location = module.data_rg.location
storage_data_lake_gen2_filesystem_id = var.filesystem_id
azuread_authentication_only = true
public_network_access_enabled = false
tags = module.data_rg.tags
}
module "workspace_admin" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-workspace-aad-admin.git?ref=v1.0.0"
synapse_workspace_id = module.synapse.id
login = "Synapse Platform Admins"
object_id = var.platform_admins_group_object_id
tenant_id = var.tenant_id
}
# Managed compute for data flows inside Azure.
module "dataflow_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-azure.git?ref=v1.0.0"
name = "ir-dataflow-prod"
synapse_workspace_id = module.synapse.id
location = module.data_rg.location
compute_type = "MemoryOptimized"
core_count = 32
time_to_live_min = 20
}
# Self-hosted compute for the systems Azure cannot reach.
module "onprem_runtime" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-synapse-integration-runtime-self-hosted.git?ref=v1.0.0"
name = "ir-onprem-sql"
synapse_workspace_id = module.synapse.id
description = "Nodes: dc1-etl01 and dc1-etl02, owned by the on-premises platform team. Reaches the finance SQL Server cluster."
}
output "runtime_names_for_pipeline_definitions" {
value = {
managed = module.dataflow_runtime.name
self_hosted = module.onprem_runtime.name
}
}
output "self_hosted_key_fingerprint" {
value = module.onprem_runtime.authorization_key_primary_fingerprint
}
output "remaining_manual_step" {
value = module.onprem_runtime.this_registration_runs_nothing_until_a_node_is_installed
}🚨 This composition is not finished when it applies cleanly. The self-hosted runtime is registered and has no nodes; someone has to install the software on
dc1-etl01anddc1-etl02and register each with an authorization key. Treatremaining_manual_stepas the reminder that it is owed.
🔴 Note what the two runtimes cost you in access terms: planning this configuration reads the self-hosted runtime's keys. If the pipeline that plans it is not one you would trust with a route into the finance network, split the self-hosted runtime into its own state and its own pipeline.
Identity — name and synapse_workspace_id, both required and both force-new.
Documentation — description, optional and the only field that updates in place.
Universal tail — timeouts only. This resource has no tags and no location.
Full input schema
| Name | Type | Default | Required | Notes |
|---|---|---|---|---|
name |
string |
— | yes | Letters, numbers and single dashes, first and last alphanumeric. Force-new. A one-character name is accepted by the provider despite its message. |
synapse_workspace_id |
string |
— | yes | Full Synapse workspace Resource ID, anchored at both ends. Force-new. |
description |
string |
null |
no | Free text. Must be non-empty when supplied. The only place to record which machines are registered. |
timeouts |
object({ create, read, update, delete }) |
null |
no | Go duration strings. Provider defaults: create 30m, read 5m, update 30m, delete 30m. |
⚠️ A key not declared in thetimeoutsobject type is silently discarded by Terraform's type conversion rather than reported — a misspelled key produces no error and no timeout.
ℹ️
readcovers two API calls: the runtime, and then its authorization keys through a separate client. Both happen inside that single timeout.
There are no key inputs. Both authorization keys are Computed — Azure generates them, and the provider reads them back.
| Output | Description | Notes |
|---|---|---|
id |
Resource ID of the runtime | Emitted first |
name |
The string a pipeline names, and a node reports against | Force-new |
synapse_workspace_id |
The parent workspace, as Azure holds it | Rebuilt from the record's own ID |
synapse_workspace_name |
Workspace name, parsed from that ID | Derived locally |
resource_group_name |
Resource group, parsed from that ID | Derived locally |
description |
The description as Azure holds it | null when unset |
authorization_key_primary_fingerprint |
SHA-256 of the primary key | The key itself is never emitted |
authorization_key_secondary_fingerprint |
SHA-256 of the secondary key | The key itself is never emitted |
authorization_keys_are_distinct |
True when the two keys differ | A false is worth investigating |
reading_this_resource_reads_its_credentials |
Constant true |
Read this one first |
the_provider_does_not_redact_these_keys |
Constant true |
Control review |
the_keys_are_in_state_in_plaintext |
Constant true |
Secret handling |
this_registration_runs_nothing_until_a_node_is_installed |
Constant true |
Design review |
a_missing_key_response_removes_this_from_state |
Constant true |
Troubleshooting |
name_is_shorter_than_three_characters |
True when the name relies on a provider gap | Naming review |
the_two_runtime_types_do_not_share_a_name_rule |
Constant true |
Naming review |
there_is_no_location_on_this_resource |
Constant true |
Design review |
force_new_fields |
["name", "synapse_workspace_id"] |
A replacement issues new keys |
fields_azure_returns_on_read |
The fields in which drift is detectable at all | Drift review |
this_resource_supports_no_azure_resource_tags |
Constant true |
Tagging policy |
🔒 Neither authorization key is emitted, in any form but a hash. The provider does not mark them sensitive, which makes re-emitting worse than usual — the value would be copied into every consuming configuration's state and printed in the clear in any plan that shows it.
Reading this resource reads its credentials, and that is a permissions decision rather than a footnote.
The provider's read path fetches the runtime and then calls a second client to list its two authorization
keys — on every refresh, whether or not anything references them. So terraform plan needs
listAuthKeys/action, and anyone who can plan can obtain the keys that register a node against this runtime.
Where the runtime's whole purpose is a route into a network Azure cannot otherwise reach, that is the most
consequential fact about the resource, and it belongs in the conversation before plan rights are granted.
The provider redacts nothing, and state is not encrypted by any marking. Both keys are plain Computed
strings in the schema. This module therefore emits fingerprints and a distinctness flag instead of the
values — enough to detect a rotation, useless to an attacker — but it cannot stop a caller from referencing
the resource attribute directly, and nothing protects that reference. The keys are also written into state on
every refresh, so the state file holds live registration credentials; sensitive = true would redact plan
output and would not encrypt state, which is why the honest control is an encrypted, access-controlled
backend rather than a marking.
The record is a registration, and the compute is somewhere else. Until the runtime software is installed
on a machine and registered with one of these keys, the runtime exists, appears healthy in Studio, and runs
nothing. Terraform cannot see whether a node is attached, how many there are, or whether they are patched —
the nodes are not Azure resources. That is why description carries more weight here than on most resources:
it is the only field on the record where the node inventory can live, and there is no tags argument either.
A not-found on the key listing drops the runtime from state. The read fetches the runtime successfully and then, if the key-listing call returns not-found, clears the resource ID anyway. The record disappears from state, the next plan proposes a create, and that create fails against the runtime that is still there — this resource does have an existence check. The recovery is an import, and the symptom gives no hint of the cause.
A replacement is an operations task. name and synapse_workspace_id are both force-new, and a
replacement issues new authorization keys. Every registered machine stops working and has to be
re-registered by hand with a key someone fetches and carries. Only description updates in place.
The two runtime types share an error message and not a name rule. This resource forbids consecutive
dashes and accepts a one-character name; the Azure runtime enforces a three-character minimum and permits
consecutive dashes. Each provider message is wrong about the half the other enforces. This module mirrors its
own pattern and reports the gap through name_is_shorter_than_three_characters rather than inventing the
missing check — a rule the provider does not enforce may be one Azure does not enforce either, and a
validation {} failure blocks terraform destroy as well as apply.
| Concern | Default in this module | Opt-out |
|---|---|---|
| The authorization keys | Never emitted, in any form but a SHA-256 hash | none — read them from Azure at the moment you need one |
| The state exposure | Stated plainly: the keys are in state in plaintext, and no marking changes that | — |
| The refresh-time permission | Listed in the RBAC table as a requirement of every plan, not of an explicit key read | — |
| The name pattern | Mirrors the provider's regex, not its message. The minimum-length rule the message claims is reported, never enforced | — |
| The missing node | Reported through a constant output — Terraform cannot see one, so it will not pretend to | — |
| The state-drop behaviour | Reported, with the import command given in the examples | — |
tags / location |
Not offered — the resource has neither. Tag the workspace | — |
🔒 The secure-by-default rule cannot apply to the keys: they are Computed, so there is no input to make safe and no risky option to hide behind extra typing. The module compensates the three ways this suite prescribes — it never emits them, it names the exposure in the outputs and the permissions table, and it publishes the one derived bit worth reviewing (
authorization_keys_are_distinct) instead of the values.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module at a tag — ?ref=v1.0.0 — never a branch. Everything above is plan-only static analysis; a
human applies from CI.
What terraform validate covers, offline, with no credentials:
- Every
validation {}block in this module — the name pattern and its Resource-ID lookalike check, the anchoredsynapse_workspace_idshape, thedescriptionnon-empty check, and thetimeoutsduration shapes. - Type conversion of the
timeoutsobject — with the caveat that undeclared keys are discarded silently rather than reported.
What only terraform plan reaches (credentials required):
- That the workspace exists and that its ID parses.
- That the caller's identity holds
Microsoft.Synapse/workspaces/integrationRuntimes/writeon it. - That no runtime of this name already exists — this resource does have an existence check.
- The authorization keys themselves. A plan reads them; that is not a side effect of anything you asked for.
What neither reaches, at any stage:
- Whether any node is installed, registered, patched or running.
- Whether any pipeline or linked service selects this runtime. The reference is a string Terraform cannot see.
- Whether the machine the runtime is meant to run on can actually reach the data source.
Outputs:
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-data-platform/providers/Microsoft.Synapse/workspaces/syn-platform-prod/integrationRuntimes/ir-onprem-sql"
name = "ir-onprem-sql"
synapse_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-data-platform/providers/Microsoft.Synapse/workspaces/syn-platform-prod"
synapse_workspace_name = "syn-platform-prod"
resource_group_name = "rg-data-platform"
description = "Nodes: dc1-etl01 and dc1-etl02, owned by the on-premises platform team."
authorization_key_primary_fingerprint = "42f1591a058c9632545904caea09ddd890ba552d17a506a0f28dba9400eb3bf1"
authorization_key_secondary_fingerprint = "b30d62604732d1e818387a86684973343356ef877f400613d67fb5d4789c1324"
authorization_keys_are_distinct = true
reading_this_resource_reads_its_credentials = true
name_is_shorter_than_three_characters = false
force_new_fields = [
"name",
"synapse_workspace_id",
]
| Symptom | Cause | Fix |
|---|---|---|
| The runtime shows as unavailable in Synapse Studio | No node is installed and registered against it | Install the runtime software on a machine and register it with an authorization key. See this_registration_runs_nothing_until_a_node_is_installed |
terraform plan fails on a permissions error mentioning auth keys |
Every refresh lists the authorization keys, which needs listAuthKeys/action |
Grant it — or accept that this identity cannot plan this resource |
| A plan printed the authorization keys | The provider marks neither key sensitive | Expected, and it is the reason this module never emits them. Treat the log as containing credentials |
| The runtime disappeared from state and the next apply fails to create it | A not-found on the key listing clears the resource ID even though the runtime was found | Re-import. See a_missing_key_response_removes_this_from_state |
| Nodes stopped working after a rename | name is force-new, and a replacement issues new authorization keys |
Re-register every node. Renaming is an operations task, not an edit |
| A key rotation cannot be confirmed | The keys are never printed, by design | Compare authorization_key_primary_fingerprint before and after |
| Both fingerprints are identical | The two authorization keys are the same value, which defeats having two | Regenerate one of them in Azure |
| A one-character name was accepted here and rejected on the Azure runtime | The two resources have different regular expressions and the same error message | Use at least 3 characters and single dashes — legal on both |
A resource with the ID … already exists |
This resource has a real existence check, and a runtime of that name is already there | terraform import it rather than creating |
Error: Missing required argument: features |
The caller has no provider "azurerm" { features {} } block |
Add one to the root module. This module declares no provider block |
azurerm_synapse_integration_runtime_self_hosted— the provider resource this module wraps.azurerm_synapse_integration_runtime_azure— the managed runtime, a different resource.- Self-hosted integration runtime — Microsoft's platform documentation for installing and registering nodes.
- Integration runtime in Azure Data Factory and Synapse Analytics — what each runtime type is for.
terraform-azurerm-synapse-workspace— the parent workspace.terraform-azurerm-synapse-integration-runtime-azure— the managed runtime.terraform-azurerm-synapse-linked-service— names this runtime to reach a target Azure cannot.terraform-azurerm-key-vault— where an authorization key belongs if it must be stored at all.- This module's
SCOPE.md— the cross-module contract.
💙 "Infrastructure as Code should be standardized, consistent, and secure."