Skip to content

microsoftexpert/tf-mod-powerplatform-connection

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔌 Power Platform Connection Terraform Module

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 and sensitive, 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.

Terraform provider module type resources


🧩 Overview

  • 🔗 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_set as sensitive = 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's id and name outputs.
  • 🧭 Environment-specific identity: this module's id output has no meaning outside the environment_id it 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 plan from either leaking a secret to a CI log or nagging every run with a diff that isn't real.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

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!


🗺️ Where this fits

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;
Loading

🧬 What this builds

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;
Loading

Resource inventory: one resource, powerplatform_connection.this. No for_each children.


✅ Provider / Versions

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_set are plain JSON-encoded strings, not typed object attributes — 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.tf applies lifecycle { ignore_changes = [...] } on both parameter attributes to avoid a perpetual false diff, mirroring the provider's own documented example.
  • name is the connector type name (e.g. "shared_sql"), not a caller-chosen unique instance identifier — look it up via the powerplatform_connectors data source.
  • status is a read-only Set 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.tf for this case.

🔑 Required Entra App Registration Permissions & Power Platform Admin Role

  • 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.

Power Platform Prerequisites

  • The target environment (var.environment_id) must already exist.
  • The connector named by var.name must be available in the target environment/tenant (query powerplatform_connectors to 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.

📁 Module Structure

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

⚙️ Quick Start

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 powerplatform provider block (Azure CLI, Service Principal + secret/certificate, OIDC, or Managed Identity) — this module never references authentication.


🔌 Cross-Module Contract

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

📚 Example Library

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_parameters at 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_set is sensitive = true — Terraform will redact it from plan/ apply console 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 — each module block produces an independent connection instance with its own id.

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 in lifecycle.ignore_changes, rotating client_secret here requires an explicit -replace, not a normal apply — 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 id and name outputs are exactly what tf-mod-powerplatform-connection-share needs as connection_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 }
    }
  })
}

⚠️ Because connection_parameters_set is permanently ignored for changes after creation, a secret rotation must force replacement explicitly — a plain apply after 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"
    }
  }
}

📥 Inputs

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 = {}
}

🧾 Outputs

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

🧠 Architecture Notes

  • connection_parameters and connection_parameters_set are 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 to any (which this house standard forbids). Marking both sensitive = true is the practical secure default given that constraint.
  • The lifecycle block is unconditional, not caller-configurable. Terraform's ignore_changes meta-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 normal apply — see Troubleshooting for the -replace workaround.
  • name vs id vs display_name are three different things: name is the connector type (shared across many connection instances), id is this specific instance's unique GUID, and display_name is a caller-chosen label. tf-mod-powerplatform-connection-share needs both id and name together — do not assume name alone is sufficient to identify a share target.

🧱 Design Principles

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

🚀 Runbook

cd tf-mod-powerplatform-connection
terraform init -backend=false
terraform validate
terraform fmt -check

Pin 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.


🧪 Testing

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.


💬 Example Output

$ 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",
]

🔍 Troubleshooting

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

🔗 Related Docs


💙 "Infrastructure as Code should be standardized, consistent, and secure."

Packages

 
 
 

Contributors

Languages