Provisions a single
powerplatform_connection— an authenticated instance of a connector (SQL, Azure OpenAI, SharePoint, and hundreds of others) inside one Power Platform environment. Connector-specific auth material stays JSON-shaped andsensitive, and update-noise from partially-masked API responses is handled the same way the provider's own examples do. Built for microsoft/power-platform ~> 4.1.
- 🔗 Provisions one
powerplatform_connection— the authenticated bridge between an environment's apps/flows and an external data source or service. - 🔒 Marks
connection_parameters/connection_parameters_setassensitive = true— connector auth payloads frequently embed credential material (API keys, client secrets) directly in JSON. - 🧯 Applies the provider's own documented
lifecycle { ignore_changes = [...] }pattern on both parameter attributes, so a partially-masked API read doesn't produce a perpetual false plan diff. - 🚫 Never shares the connection with any other principal — that is
tf-mod-powerplatform-connection-share's job, consuming this module'sidandnameoutputs. - 🧭 Environment-specific identity: this module's
idoutput has no meaning outside theenvironment_idit was created in.
💡 Why it matters: connections are the single most likely place in this provider's schema to carry credential material directly in Terraform state and plan output. Getting the sensitivity marking and the ignore-changes behavior right here is what keeps a
terraform planfrom either leaking a secret to a CI log or nagging every run with a diff that isn't real.
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!
graph TD
ENV["tf-mod-powerplatform-environment"]:::keystone
CONN["tf-mod-powerplatform-connection (this module)"]:::this
CS["tf-mod-powerplatform-connection-share"]:::neutral
CONNECTORS["powerplatform_connectors data source"]:::neutral
ENV -->|"environment_id"| CONN
CONNECTORS -->|"name (connector type)"| CONN
CONN -->|"connection_id + connector_name"| CS
ENV -->|"environment_id scope"| CS
classDef this fill:#742774,color:#fff,stroke:#742774;
classDef keystone fill:#4B1D4B,color:#fff,stroke:#4B1D4B;
classDef neutral fill:#E8E8E8,color:#222,stroke:#999;
graph TD
D1["var.environment_id"]:::input
D2["var.name (connector type)"]:::input
D3["var.display_name"]:::input
D4["var.connection_parameters (sensitive)"]:::input
D5["var.connection_parameters_set (sensitive)"]:::input
D6["var.timeouts"]:::input
R["powerplatform_connection.this"]:::this
O1["output: id"]:::output
O2["output: name"]:::output
O3["output: display_name"]:::output
O4["output: environment_id"]:::output
O5["output: status"]:::output
D1 -->|"environment_id"| R
D2 -->|"name"| R
D3 -->|"display_name"| R
D4 -->|"connection_parameters (ignore_changes)"| R
D5 -->|"connection_parameters_set (ignore_changes)"| R
D6 -->|"timeouts"| R
R -->|"id"| O1
R -->|"name"| O2
R -->|"display_name"| O3
R -->|"environment_id"| O4
R -->|"status"| O5
classDef input fill:#E8E8E8,color:#222,stroke:#999;
classDef this fill:#742774,color:#fff,stroke:#742774;
classDef output fill:#E8E8E8,color:#222,stroke:#999;
Resource inventory: one resource, powerplatform_connection.this. No for_each children.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
microsoft/power-platform |
~> 4.1 |
| Provider block | None — the caller's root module configures authentication |
Schema notes that bite:
connection_parameters/connection_parameters_setare plain JSON-encoded strings, not typedobjectattributes — the live schema treats connector-specific auth shape as opaque, since it varies per connector. This module does not (and cannot) type it more strictly.- The Power Platform API does not always echo back every submitted parameter value on read (some
are masked/omitted) —
main.tfapplieslifecycle { ignore_changes = [...] }on both parameter attributes to avoid a perpetual false diff, mirroring the provider's own documented example. nameis the connector type name (e.g."shared_sql"), not a caller-chosen unique instance identifier — look it up via thepowerplatform_connectorsdata source.statusis a read-onlySet of String; there is no writable status/enable-disable attribute on this resource.- A 404 on read/delete is treated as "already deleted" by the current provider version — no
defensive workaround is needed in
main.tffor this case.
- Environment Maker role, or higher, within the target environment.
- Confirm current Power Platform API app-registration permission scope names against the provider's Authentication guide at authoring/deployment time — these scope names have been renamed across provider versions.
- The target environment (
var.environment_id) must already exist. - The connector named by
var.namemust be available in the target environment/tenant (querypowerplatform_connectorsto confirm the exact value). - Any credential material required by the target connector must be sourced out of band (Key Vault, CI/CD secret store) — this module never generates or mints credentials.
tf-mod-powerplatform-connection/
├── providers.tf # required_providers + required_version — no provider {} block
├── variables.tf # environment_id, name, display_name, connection_parameters(_set), timeouts
├── main.tf # single resource "powerplatform_connection" "this"
├── outputs.tf # id, name, display_name, environment_id, status
├── README.md # this file
├── SCOPE.md # lightweight standalone scope
└── examples/ # runnable example configurations
module "sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
}ℹ️ The caller's root module configures the
powerplatformprovider block (Azure CLI, Service Principal + secret/certificate, OIDC, or Managed Identity) — this module never references authentication.
Consumes
| Input | Type | Source module |
|---|---|---|
environment_id |
string |
tf-mod-powerplatform-environment (.id) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Connection GUID — environment-specific | tf-mod-powerplatform-connection-share (connection_id) |
name |
Connector type name | tf-mod-powerplatform-connection-share (connector_name) |
display_name |
Display name of the connection | Reporting |
environment_id |
Pass-through of the hosting environment id | Cross-referencing |
status |
Read-only connection status strings | Reporting/troubleshooting |
1 · Minimal interactive/OAuth connection (no parameters)
module "sharepoint_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sharepointonline"
display_name = "Claims Documents — SharePoint"
}💡 Interactive/OAuth connectors need no
connection_parametersat all — leave both parameter variables unset.
2 · Service-principal-authenticated SQL connection
module "sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
connection_parameters_set = jsonencode({
"name" : "oauthSP",
"values" : {
"token" : { "value" : "https://global.consent.azure-apim.net/redirect/sql" },
"token:TenantId" : { "value" : var.tenant_id },
"token:clientId" : { "value" : var.client_id },
"token:clientSecret" : { "value" : var.client_secret }
}
})
}🔒
connection_parameters_setissensitive = true— Terraform will redact it fromplan/applyconsole output, but it is still stored in state. Protect state access accordingly.
3 · Azure OpenAI connection with API key
module "openai_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_azureopenai"
display_name = "Underwriting Copilot — OpenAI Connection"
connection_parameters = jsonencode({
"azureOpenAIResourceName" : var.azure_openai_resource_name,
"azureOpenAIApiKey" : var.azure_openai_api_key,
"azureSearchEndpointUrl" : var.azure_search_endpoint_url,
"azureSearchApiKey" : var.azure_search_api_key
})
}4 · Custom per-operation timeouts for a slow-provisioning connector
module "slow_connector" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_commondataserviceforapps"
display_name = "Dataverse — Slow Region"
timeouts = {
create = "20m"
delete = "10m"
}
}5 · Multiple connections of the same connector type in one environment
module "sql_prod" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — Production"
}
module "sql_reporting" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — Read-Only Reporting"
}ℹ️
name(the connector type) is not unique per connection — eachmoduleblock produces an independent connection instance with its ownid.
6 · Connection scoped to a Sandbox dev environment
module "dev_sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.dev_environment.id
name = "shared_sql"
display_name = "Claims DB — Dev"
}7 · Connection scoped to a Production environment
module "prod_sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.prod_environment.id
name = "shared_sql"
display_name = "Claims DB — Production"
connection_parameters_set = jsonencode({
"name" : "oauthSP",
"values" : {
"token" : { "value" : "https://global.consent.azure-apim.net/redirect/sql" },
"token:TenantId" : { "value" : var.tenant_id },
"token:clientId" : { "value" : var.client_id },
"token:clientSecret" : { "value" : var.client_secret }
}
})
}
⚠️ Because both parameter attributes are permanently inlifecycle.ignore_changes, rotatingclient_secrethere requires an explicit-replace, not a normalapply— see Troubleshooting.
8 · Connection intended for later sharing with a team
module "team_shared_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Regulatory Reporting — Team SQL Connection"
}💡 This connection's
idandnameoutputs are exactly whattf-mod-powerplatform-connection-shareneeds asconnection_id/connector_name— see example 15 below.
9 · Web/HTTP connector with a static bearer token
module "http_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_webcontents"
display_name = "Partner API — Bearer Token"
connection_parameters = jsonencode({
"token" : var.partner_api_bearer_token
})
}10 · Connection with no display customization beyond the connector default
module "onedrive_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_onedriveforbusiness"
display_name = "OneDrive for Business"
}11 · Referencing an environment created elsewhere in the same root module
module "environment" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-environment.git?ref=v1.0.0"
display_name = "Regulatory Reporting — Dev"
location = "unitedstates"
}
module "connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Regulatory Reporting — SQL"
}12 · Reading back connection status for troubleshooting
module "connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
}
output "connection_status" {
value = module.connection.status
}13 · Rotating credential material via explicit replacement (documented limitation)
# terraform apply -replace='module.sql_connection.powerplatform_connection.this'
module "sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
connection_parameters_set = jsonencode({
"name" : "oauthSP",
"values" : {
"token" : { "value" : "https://global.consent.azure-apim.net/redirect/sql" },
"token:TenantId" : { "value" : var.tenant_id },
"token:clientId" : { "value" : var.client_id },
"token:clientSecret" : { "value" : var.rotated_client_secret }
}
})
}
⚠️ Becauseconnection_parameters_setis permanently ignored for changes after creation, a secret rotation must force replacement explicitly — a plainapplyafter only changing this variable's value will not pick up the new secret.
14 · Non-English / regional connector display naming
module "eu_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.eu_environment.id
name = "shared_sql"
display_name = "Kundendatenbank — SQL-Verbindung"
}15 · 🏗️ End-to-end composition — environment → connection → connection share
module "environment" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-environment.git?ref=v1.0.0"
display_name = "Claims Processing — Dev"
location = "unitedstates"
}
module "sql_connection" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection.git?ref=v1.0.0"
environment_id = module.environment.id
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
connection_parameters_set = jsonencode({
"name" : "oauthSP",
"values" : {
"token" : { "value" : "https://global.consent.azure-apim.net/redirect/sql" },
"token:TenantId" : { "value" : var.tenant_id },
"token:clientId" : { "value" : var.client_id },
"token:clientSecret" : { "value" : var.client_secret }
}
})
}
module "connection_share" {
source = "git::https://github.com/microsoftexpert/tf-mod-powerplatform-connection-share.git?ref=v1.0.0"
connection_shares = {
claims_team_lead = {
connection_id = module.sql_connection.id
connector_name = module.sql_connection.name
environment_id = module.environment.id
principal_object_id = var.claims_team_lead_object_id
role_name = "CanEdit"
}
}
}| Group | Variables |
|---|---|
| Primary identity | environment_id, name, display_name |
| Connector auth (sensitive) | connection_parameters, connection_parameters_set |
| Universal tail | timeouts |
Full variables.tf schema
variable "environment_id" {
type = string
}
variable "name" {
type = string # connector type name, e.g. "shared_sql"
}
variable "display_name" {
type = string
}
variable "connection_parameters" {
type = string # JSON-encoded
default = null
sensitive = true
}
variable "connection_parameters_set" {
type = string # JSON-encoded
default = null
sensitive = true
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = {}
}| Output | Description | Sensitive/Conditional |
|---|---|---|
id |
Connection GUID (primary output), environment-specific | — |
name |
Connector type name | — |
display_name |
Display name of the connection | — |
environment_id |
Pass-through of the hosting environment id | — |
status |
Read-only connection status strings | — |
connection_parametersandconnection_parameters_setare opaque JSON strings, by design. The live schema does not expose a structured object for connector-specific parameters — shape varies per connector, so this module cannot type it more strictly without either going stale connector-by-connector or falling back toany(which this house standard forbids). Marking bothsensitive = trueis the practical secure default given that constraint.- The
lifecycleblock is unconditional, not caller-configurable. Terraform'signore_changesmeta-argument cannot reference a variable — it must be a static list. This module hardcodes it on both parameter attributes because the provider's own official example does the same, to avoid a perpetual false diff from partially-masked API reads. The tradeoff: a caller who changes either variable's value after initial creation will not see that change applied via a normalapply— see Troubleshooting for the-replaceworkaround. namevsidvsdisplay_nameare three different things:nameis the connector type (shared across many connection instances),idis this specific instance's unique GUID, anddisplay_nameis a caller-chosen label.tf-mod-powerplatform-connection-shareneeds bothidandnametogether — do not assumenamealone is sufficient to identify a share target.
| Default | Opt-out |
|---|---|
connection_parameters / connection_parameters_set default to null (no connector auth payload) |
Caller supplies a jsonencode({...}) payload matching the target connector's documented shape |
Both parameter variables are sensitive = true |
Not configurable — this module never emits connector auth material in plan/apply console output |
No tags variable (this provider has no tagging concept) |
N/A — not applicable to this provider |
No provider {} block, no credential-shaped variable |
Caller's root module configures auth |
cd tf-mod-powerplatform-connection
terraform init -backend=false
terraform validate
terraform fmt -checkPin consumption at ?ref=v1.0.0, never a branch. This is a plan-only authoring process — a human
runs terraform apply from CI after review, against a sub-production environment first.
terraform validate and fmt -check confirm the configuration is internally consistent and
correctly typed — they do not confirm the Power Platform API will accept a given connector
name or connection_parameters payload for your specific tenant/connector version. Only a live
terraform plan against a real tenant exercises those constraints; this module's authoring process
is plan-only and does not perform that exercise.
$ terraform output
id = "7e2a9c1b-4f6d-4b3a-9e2c-1d5a8f6b3c40"
name = "shared_sql"
display_name = "Claims DB — SQL Connection"
environment_id = "5c1e9a2b-6f3d-4e7a-9b1c-8a2d5f6e3c40"
status = [
"Connected",
]
| Symptom | Cause | Fix |
|---|---|---|
Changing connection_parameters_set doesn't take effect on apply |
lifecycle.ignore_changes is hardcoded on this attribute (see Architecture Notes) |
Force replacement: terraform apply -replace='<module_address>.powerplatform_connection.this' |
terraform plan shows a diff on connection_parameters on every run despite no config change |
The API is masking/omitting part of the submitted value on read | Expected without the ignore_changes workaround; confirm main.tf still has the lifecycle block intact |
name rejected at apply time as an unknown connector |
Wrong connector name string | Query the powerplatform_connectors data source for the exact name value in your tenant |
Connection created but tf-mod-powerplatform-connection-share fails to find it |
Mismatched environment_id between the two modules |
Connection identity is environment-specific — confirm both modules reference the same environment |
| Sensitive value appears unexpectedly in a log | A tool outside Terraform (e.g. a custom script) printed state directly | sensitive = true only redacts Terraform's own console output, not raw state file contents — protect state storage/access separately |
- microsoft/power-platform provider docs
tf-mod-powerplatform-environment,tf-mod-powerplatform-connection-share(common siblings)- This module's
SCOPE.md
💙 "Infrastructure as Code should be standardized, consistent, and secure."