Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔷 Dynatrace xMatters Notification Terraform Module

Manages a single dynatrace_xmatters_notification resource — an xMatters webhook notification channel wired to a Dynatrace alerting profile. Targets dynatrace-oss/dynatrace ~> 1.98 (confirmed against the live v1.100.0 schema).

Terraform Dynatrace Provider module type resources posture


🧩 Overview

  • 📨 Manages one dynatrace_xmatters_notification Settings 2.0 object — an xMatters webhook integration that Dynatrace calls when a problem matches an alerting profile's rules.
  • 🧱 Renders the resource's only nested structure, headers, from a caller-keyed map(object({...})) of HTTP headers entirely inside main.tf — there is no separate Terraform resource for headers; they live inside the one keystone resource block.
  • 🔒 Ships two secure-by-default toggles baked into the variable schema: the notification is created disabled (active = false) and TLS certificate validation stays enforced (insecure = false) until a caller deliberately opts out of either.
  • 🔌 Has no owned sibling modules; consumes exactly one cross-module input — the alerting profile's id, via profile.

💡 Why it matters: An xMatters channel that auto-activates on creation, or that silently disables TLS validation, can either page the wrong on-call rotation the moment apply runs, or quietly weaken the transport security of every problem notification sent to xMatters. Both behaviors default off in this module so a human reviews the configuration — URL, headers, payload, and target alerting profile — before either risk goes live.


❤️ 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

flowchart LR
 AP["terraform-dynatrace-alerting-profile"]:::sibling
 THIS["terraform-dynatrace-xmatters-notification"]:::thismodule
 RES["dynatrace_xmatters_notification"]:::keystone

 AP -->|"emits id, consumed as profile"| THIS
 THIS -->|"renders one keystone resource"| RES

 classDef thismodule fill:#1496FF,color:#FFFFFF,stroke:#0A2540,stroke-width:1px;
 classDef keystone fill:#0A2540,color:#FFFFFF,stroke:#0A2540,stroke-width:1px;
 classDef sibling fill:#E8E8E8,color:#333333,stroke:#B0B0B0,stroke-width:1px;
Loading

Validated via the Mermaid Chart MCP (valid: true) before embedding. This module has no owned sibling modules of its own; terraform-dynatrace-alerting-profile is the one upstream module every notification-channel module in this catalog (Slack, webhook, email, Jira, OpsGenie, PagerDuty, ServiceNow, Trello, VictorOps, xMatters, Ansible Tower) attaches to via profile.


🧬 What this builds

flowchart TB
 NAME["var.name - required string"]
 PROFILE["var.profile - required string, alerting-profile id"]
 URL["var.url - required string"]
 PAYLOAD["var.payload - required string"]
 ACTIVE["var.active - optional bool, default false"]
 INSECURE["var.insecure - optional bool, default false"]
 LEGACY["var.legacy_id - optional string, default null"]
 HEADERS["var.headers - map of object, default empty map"]

 RES["dynatrace_xmatters_notification.this"]:::keystone
 HBLOCK["headers block - max_items 1"]:::nested
 HDR["dynamic header block - one per map entry, name = map key"]:::nested

 NAME --> RES
 PROFILE --> RES
 URL --> RES
 PAYLOAD --> RES
 ACTIVE --> RES
 INSECURE --> RES
 LEGACY --> RES
 HEADERS -->|"rendered only when length greater than 0"| HBLOCK
 HBLOCK --> HDR
 HDR -->|"secret_value wrapped in sensitive at point of use"| RES

 RES -->|"id"| OUT_ID["output: id"]
 RES -->|"name"| OUT_NAME["output: name"]

 classDef keystone fill:#0A2540,color:#FFFFFF,stroke:#0A2540,stroke-width:1px;
 classDef nested fill:#1496FF,color:#FFFFFF,stroke:#0A2540,stroke-width:1px;
Loading

Validated via the Mermaid Chart MCP (valid: true) before embedding.

Resource inventory:

Item Count Notes
Keystone resource 1 dynatrace_xmatters_notification.this
Owned child resources 0 Standalone primitive — no for_each-driven sibling resources
Nested block types 1 headers (max_items = 1) wrapping a repeatable header set block (min_items = 1 once present)
Data sources 0 None

ℹ️ headers is not a separate child resource wired with a resource-level for_each — it is a nested block inside the one keystone resource, populated via a dynamic "headers" block whose inner dynamic "header" block iterates var.headers. The map-keyed shape gives callers stable, name-addressable configuration and enforces unique header names at plan/validate time; Terraform's own set-block diffing (see Architecture Notes) already handles the underlying header set block's ordering independently of this.


✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
dynatrace-oss/dynatrace ~> 1.98 (confirmed against the live v1.100.0 schema)
provider {} block None — the caller configures the dynatrace provider (auth + dt_env_url) once, outside this module

Schema notes that bite:

  • No top-level attribute on dynatrace_xmatters_notification is provider-flagged sensitive — name, profile, url, payload, active, insecure, and legacy_id are all plain. Only the nested header.secret_value attribute, inside the headers block, is sensitive = true in the live schema.
  • secret_value is never emitted as a module output. outputs.tf omits the entire headers collection (and every individual header attribute) — not just the secret one — so there is no path for any header material, secret or otherwise, to leave this module via an output.
  • insecure (TLS certificate validation bypass for the outbound call Dynatrace makes to your xMatters endpoint) defaults to false in this module's variable, even though the live schema documents no provider-side default at all for this field — the module bakes in the safe value itself rather than deferring to undocumented API behavior.
  • active is required: true in the live provider schema, with no server-side default — every apply must send an explicit true/false. This module absorbs that requirement by defaulting the caller-facing variable to false, so main.tf always renders a concrete boolean: a newly-applied integration is inert (disabled) until a caller explicitly sets active = true.
  • payload is also required: true in the live schema with no documented default — modeled here as a required variable; there is no safe "empty payload" fallback to default to.
  • headers is a single-instance wrapper block (nesting_mode = "list", max_items = 1) around a repeatable header set block (min_items = 1 once the wrapper is present at all). main.tf renders the headers {} wrapper only when the caller's map is non-empty, so omitting headers (or leaving it at its default {}) never produces an invalid empty wrapper block.
  • legacy_id is optional and computed — modeled as a nullable, caller-settable variable rather than omitted or made required, per house convention for computed-but-settable fields. Leave it null unless migrating a pre-Settings-2.0 reference that another REST API v1-based resource already keys against.
  • No force-new/immutable field is asserted for this resource in this README: the live terraform providers schema -json schema dump used to author this module does not expose SDK-level ForceNew flags (that dump reports type/required/optional/computed/sensitive only). Treat any argument change as a potential in-place update per the general Settings 2.0 convention unless you observe replace-then-create behavior in a real terraform plan — do not assume this README's silence on force-new fields means none exist.

🔑 Required OAuth Scopes / API Token Permissions

  • API token (confirmed against the resource's own live documentation): settings.read ("Read settings"), settings.write ("Write settings"). This corrects an earlier draft that used the classic Config API v1 permission names (ReadConfig/WriteConfig), which do not apply to this Settings-2.0-backed resource.
  • OAuth (the recommended path, per general Settings-2.0 guidance): settings:objects:read, settings:objects:write — not independently confirmed on this resource's own documentation page, which states only the API-token scopes above. Verify against your own environment/IAM policy before a production OAuth grant.
  • This module does not touch IAM or Automation resources — no account-idm-* or automation:* scopes are needed for this module specifically.

ℹ️ Confidence note: the resource's own provider-registry documentation page was not directly queried to confirm an explicit "accepted authentication mechanisms" statement during this authoring pass — the scopes above are carried over from the confirmed dual-auth pattern of this resource's Settings-2.0 siblings in the same schema dump, not from a resource-specific authentication note. Verify against your environment (or the live provider docs) before treating this as final for a production change that depends on it.


Dynatrace Prerequisites

  • An OAuth client (or legacy API token) provisioned with the scopes/permissions above.
  • A Dynatrace alerting profile must already exist (or be created in the same plan) — its id is a required input to this module via profile.
  • An xMatters integration/webhook endpoint already provisioned on the xMatters side, with its inbound webhook URL available to pass as url.
  • If the xMatters endpoint requires custom HTTP headers (e.g. an API key header), have those values available from your own secrets manager before authoring the calling configuration — this module never accepts literal secret defaults.

📁 Module Structure

File Role
providers.tf required_version + required_providers pin — no provider {} block
variables.tf name, profile, url, payload, active, insecure, legacy_id, headers
main.tf Thin, total renderer — one keystone resource this, with dynamic "headers" / dynamic "header"
outputs.tf id, name — no header attribute of any kind is ever output
README.md This file
SCOPE.md Lightweight standalone-module scope: design intent, Consumes/Emits, required scopes, prerequisites
examples/quickstart/main.tf Minimal, directly-runnable example (validated with terraform init -backend=false / validate / fmt -check)

⚙️ Quick Start

module "xmatters_notification" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-platform-team"
  profile = module.alerting_profile.id # terraform-dynatrace-alerting-profile output
  url     = "https://example.xmattersapi.com/api/xm/1/flow/platform-team-integration"
  payload = "{ProblemTitle} - {ProblemSeverity} - {ProblemURL}"
}

ℹ️ You configure the dynatrace provider (OAuth client credentials, platform token, or legacy API token, plus dt_env_url) once, outside this module — this module never declares a provider {} block or an auth-related variable. See examples/quickstart/main.tf for a complete, directly-runnable snippet including the terraform / required_providers block.


🔌 Cross-Module Contract

Consumes

Input Type Source module
profile string, required terraform-dynatrace-alerting-profile (its id output) — referenced by ID only; this module does not manage or validate the alerting profile itself

Emits

Output Description Consumed by
id The Dynatrace object ID of this xMatters notification configuration. No current consumer — exposed for reference/import and for any future aggregation module that might relate notification channels to other objects
name The display name of this xMatters notification configuration. Reference/documentation only

📚 Example Library

1 · Minimal inert integration (secure-by-default)
module "xmatters_minimal" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-platform-team"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/platform-team-integration"
  payload = "{ProblemTitle} - {ProblemSeverity}"
}

💡 active and insecure are both omitted — they default to false. The integration is created disabled, and TLS certificate validation stays enforced. Nothing pages anyone until a caller reviews the configuration and explicitly sets active = true.

2 · Activating a reviewed integration
module "xmatters_active" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-platform-team"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/platform-team-integration"
  payload = "{ProblemTitle} - {ProblemSeverity}"
  active  = true
}

⚠️ Only set active = true after the URL, payload, and target alerting profile have been reviewed — from this point on, matching problems will page through xMatters.

3 · Rich JSON payload for downstream automation
module "xmatters_json_payload" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-json-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/json-integration"
  payload = "{ProblemDetailsJSONv2}"
}

ℹ️ {ProblemDetailsJSONv2} follows the Dynatrace Problems V2 API structure — use this when the receiving xMatters flow parses the payload as structured JSON rather than free text.

4 · Markdown-formatted payload
module "xmatters_markdown_payload" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-markdown-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/markdown-integration"
  payload = "{ProblemDetailsMarkdown}"
}
5 · HTML-formatted payload
module "xmatters_html_payload" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-html-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/html-integration"
  payload = "{ProblemDetailsHTML}"
}

ℹ️ Use {ProblemDetailsHTML} only when the receiving xMatters flow is configured to render HTML — otherwise the recipient sees raw markup.

6 · Tag-scoped payload referencing entity tags
module "xmatters_tag_scoped" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-tag-scoped-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/tag-scoped-integration"
  payload = "{ProblemTitle} - environment={Tags[environment]} - team={Tags[owning-team]}"
}

ℹ️ {Tags[key]} resolves to an empty string if the tag key exists with no value, and is left unreplaced if the tag key does not exist at all on any impacted entity.

7 · Custom HTTP headers — plain values only
module "xmatters_plain_headers" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-with-headers"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/headers-integration"
  payload = "{ProblemTitle} - {ProblemSeverity}"

  headers = {
    "Content-Type" = {
      value = "application/json"
    }
  }
}
8 · Custom HTTP headers including a secret value
variable "xmatters_api_key" {
  type      = string
  sensitive = true
}

module "xmatters_secret_header" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-authenticated-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/authenticated-integration"
  payload = "{ProblemTitle} - {ProblemSeverity}"

  headers = {
    "Content-Type" = {
      value = "application/json"
    }
    "Authorization" = {
      secret_value = var.xmatters_api_key
    }
  }
}

🔒 var.xmatters_api_key must come from your own secrets manager (e.g. Azure Key Vault) at plan time — never hardcode a literal secret in checked-in .tf/.tfvars. Internally, main.tf wraps header.value.secret_value in the built-in sensitive function at the exact point it is assigned to the resource's header.secret_value argument (see Architecture Notes for the full mechanics). This module never outputs this value, or any other header attribute, under any name.

9 · Multiple headers combined (plain, secret, and empty-value)
module "xmatters_multi_header" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-multi-header-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/multi-header-integration"
  payload = "{ProblemTitle} - {ProblemSeverity}"

  headers = {
    "Content-Type"    = { value = "application/json" }
    "X-Requested-For" = { value = "" }
    "Authorization"   = { secret_value = var.xmatters_api_key }
  }
}

💡 The map key is each header's name — adding or removing one entry never perturbs the others, and duplicate header names are rejected at plan/validate time because map keys must be unique.

10 · TLS certificate validation bypass (explicit opt-out)
module "xmatters_insecure" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name     = "xmatters-legacy-selfsigned-endpoint"
  profile  = module.alerting_profile.id
  url      = "https://legacy-xmatters-gateway.internal.example.com/api/xm/1/flow/legacy-integration"
  payload  = "{ProblemTitle} - {ProblemSeverity}"
  insecure = true
}

⚠️ insecure = true disables TLS certificate validation for every outbound call Dynatrace makes to this xMatters endpoint, removing protection against man-in-the-middle tampering. Only set this for a documented, reviewed exception (e.g. a known internal self-signed certificate chain) — never as a default workaround for a certificate error. Fix the certificate instead where possible.

11 · Migrating a legacy REST API v1 reference
module "xmatters_legacy_migration" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name      = "xmatters-migrated-integration"
  profile   = module.alerting_profile.id
  url       = "https://example.xmattersapi.com/api/xm/1/flow/migrated-integration"
  payload   = "{ProblemTitle} - {ProblemSeverity}"
  legacy_id = "1234567890123456"
}

ℹ️ Leave legacy_id unset (null, the default) unless another REST API v1-based resource in your environment already references this notification by its legacy key — Dynatrace computes and manages this value server-side otherwise.

12 · Caller-side fan-out across multiple teams
variable "xmatters_integrations" {
  type = map(object({
    url     = string
    profile = string
  }))
}

module "xmatters_per_team" {
  for_each = var.xmatters_integrations
  source   = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-${each.key}"
  profile = each.value.profile
  url     = each.value.url
  payload = "{ProblemTitle} - {ProblemSeverity} - team=${each.key}"
}

💡 This module is a standalone primitive with no owned for_each collection of its own — a caller wraps the module call itself in for_each to fan out one xMatters integration per team, environment, or on-call rotation, each still defaulting to active = false until reviewed.

13 · Dormant, fully-staged configuration awaiting change-control sign-off
module "xmatters_staged_for_review" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-new-oncall-integration"
  profile = module.alerting_profile.id
  url     = "https://example.xmattersapi.com/api/xm/1/flow/new-oncall-integration"
  payload = "{ProblemDetailsJSONv2}"

  headers = {
    "Content-Type"  = { value = "application/json" }
    "Authorization" = { secret_value = var.xmatters_api_key }
  }

  # Left false deliberately: staged for a change-control reviewer to flip once
  # the URL, payload, and headers above have been signed off.
  active = false
}

ℹ️ This is the same secure-by-default posture as Example 1, shown here with a fuller configuration to illustrate a common regulated-environment workflow: apply the complete, reviewed configuration first with active = false, then flip active = true in a follow-up, separately-reviewed change once change control has signed off.

14 · 🏗️ End-to-end composition

Wires a terraform-dynatrace-alerting-profile instance's id output into this module's profile input — the complete cross-module pattern every example above assumes.

module "alerting_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-alerting-profile.git?ref=v1.0.0"

  name = "platform-team-availability-and-errors"

  rules = {
    availability-all-entities = {
      severity_level   = "AVAILABILITY"
      include_mode     = "INCLUDE_ANY"
      delay_in_minutes = 5
      tags             = ["team:platform"]
    }
    errors-all-entities = {
      severity_level   = "ERRORS"
      include_mode     = "INCLUDE_ANY"
      delay_in_minutes = 5
      tags             = ["team:platform"]
    }
  }
}

module "xmatters_notification" {
  source = "git::https://github.com/microsoftexpert/terraform-dynatrace-xmatters-notification.git?ref=v1.0.0"

  name    = "xmatters-platform-team"
  profile = module.alerting_profile.id # <- cross-module wire-in
  url     = "https://example.xmattersapi.com/api/xm/1/flow/platform-team-integration"
  payload = "{ProblemTitle} - {ProblemSeverity} - {ProblemURL}"

  headers = {
    "Content-Type"  = { value = "application/json" }
    "Authorization" = { secret_value = var.xmatters_api_key }
  }

  # Reviewed and activated only after confirming the profile's rules and this
  # integration's headers/payload are correct.
  active = true
}

💡 profile = module.alerting_profile.id is an implicit reference, not depends_on — Terraform orders the alerting profile's creation before this notification automatically because the notification's configuration literally depends on the profile's computed id.


📥 Inputs

Required:

Name Type Description
name string Display name of the notification configuration
profile string ID of the Dynatrace alerting profile this notification attaches to — pass a terraform-dynatrace-alerting-profile instance's id output
url string The xMatters webhook URL
payload string The notification message content, supporting Dynatrace's {Placeholder} syntax

Optional:

Name Type Default Description
active bool false Enables/disables the integration — secure-by-default
insecure bool false Accepts any TLS certificate from the xMatters endpoint when true — secure-by-default
legacy_id string null REST API v1 legacy key; leave null unless migrating
headers map(object({...})) {} Extra HTTP headers, keyed by header name
Full object schemas
variable "headers" {
  type = map(object({
    value        = optional(string) # plain header value; may be empty
    secret_value = optional(string) # secret header value; may be empty — sensitive = true in the live schema
  }))
  default = {}
}

Each map entry's key is the header's name (required by the provider schema); main.tf derives the rendered name argument from the map key, so header names are guaranteed unique at plan/validate time rather than only failing at apply against a live environment.


🧾 Outputs

Output Description Notes
id The Dynatrace object ID of this xMatters notification configuration Always present
name The display name of this xMatters notification configuration Always present

ℹ️ No header attribute — plain or secret — is ever emitted as a module output. secret_value is sensitive = true in the live provider schema; this module omits the entire headers collection from outputs.tf rather than re-exporting it in any form.


🧠 Architecture Notes

  • The sensitive-at-point-of-use mechanic (the load-bearing detail in main.tf). Terraform's variable block can only mark an entire variable sensitive = true — there is no language construct to flag a single nested attribute inside a map(object(...)) type constraint. Marking the whole var.headers variable sensitive here would have two consequences, both undesirable: it would redact every header's non-secret value and the header names themselves, and — more fundamentally — Terraform's language rules do not permit a sensitive value, or a value derived from one, to be used as the collection expression driving a for_each (this includes the for_each inside a dynamic block), because the keys/values driving for_each determine resource instance addressing and are surfaced in plan output and state; using a sensitive value there would undermine the whole point of marking it sensitive. Since main.tf iterates var.headers twice — once in dynamic "headers" and again in the nested dynamic "header" — the entire map has to remain a plain, non-sensitive value for those two for_each expressions to be legal at all. Instead, main.tf leaves var.headers un-marked and wraps only the actual secret material at the exact point it is assigned into the resource argument: secret_value = try(sensitive(header.value.secret_value), null). The built-in sensitive function marks that specific returned value as sensitive from that point forward, so Terraform's plan/apply output redacts it in the rendered header block — without ever making the map, its keys, or the non-secret value field sensitive, and without tripping the for_each restriction, because the sensitivity is applied downstream of the for_each evaluation, not upstream of it. The provider's own schema independently marks header.secret_value sensitive = true, so Terraform would redact this value in plan/apply output regardless of this module's own wrapping — the explicit sensitive call is a defense-in-depth/clarity measure documented at the variable, not the sole source of redaction.
  • Ordering via implicit reference. profile is expected to be wired from a terraform-dynatrace-alerting-profile instance's id output. Passing that output directly (rather than a literal string) creates an implicit dependency, so Terraform creates the alerting profile before this notification automatically — never use depends_on as a substitute for this real reference.
  • Why headers is a map, not a list, even though the provider models header as a set. Terraform's own set-block diffing already tolerates reordering (a set block type diffs by full content, not position), so modeling var.headers as a caller-keyed map is not working around positional brittleness in the underlying header set. It exists instead to (a) give callers stable, name-addressable configuration in their own .tfvars/module calls, and (b) enforce unique header names at plan/validate time — Terraform rejects duplicate map keys outright — rather than only surfacing a duplicate-header conflict when the API rejects the apply.
  • try(x, default) on every optional field, even where the variable's own default already guarantees a non-null value (e.g. active, insecure) — defensive against a future provider or variable-schema revision reintroducing nullability, matching the sibling notification modules' convention.
  • Environment-scoped only. dynatrace_xmatters_notification is a Settings 2.0 object scoped to a single Dynatrace environment — unlike IAM/Account Management resources, there is no account-vs-environment API-surface split to document for this module.
  • No force-new claims made. The live schema dump used to author this module (terraform providers schema -json) does not expose SDK-level ForceNew flags. Do not treat this README's silence on immutable fields as confirmation that none exist — verify actual replace-vs-update behavior with a real terraform plan if this matters for your change.

🧱 Design Principles

Concern Safe default Opt-out (caller must set explicitly)
Notification activation active defaults to false — a newly-applied integration does not fire Caller sets active = true after reviewing the URL, payload, headers, and target alerting profile
TLS certificate validation insecure defaults to false — certificate validation is enforced on the outbound call to the xMatters endpoint Caller sets insecure = true explicitly, as a documented, reviewed exception
Header secret material secret_value is wrapped in sensitive at the point of use and never appears in any module output N/A — non-negotiable; source secret header values from your own secrets manager
Extra HTTP headers headers defaults to {} — no additional headers are sent unless the caller adds them Caller populates headers explicitly, keyed by header name
Legacy REST API v1 id legacy_id defaults to null — Dynatrace computes and manages it server-side Caller sets legacy_id explicitly only when migrating a pre-Settings-2.0 reference

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module source to an explicit tag — ?ref=v1.0.0 — never a branch. This library is plan-only: a human runs terraform apply from CI after reviewing the plan.


🧪 Testing

  • terraform validate / terraform fmt -check confirm: HCL syntax is well-formed, every required variable (name, profile, url, payload) is supplied by the caller, the headers map conforms to its object({...}) type constraint, and no argument not present in the provider schema is passed to dynatrace_xmatters_notification.this.
  • Only a real terraform plan (or apply) against a live Dynatrace environment can confirm: that the profile id actually resolves to an existing alerting profile, that the configured OAuth client or API token actually holds settings:objects:write / settings.write, that the xMatters url endpoint accepts the rendered payload format, and the real-world effect of insecure against that endpoint's actual certificate chain.
  • No cloud apply is performed by this authoring process — this module's static-analysis proof gate ends at validate/fmt, consistent with this library's plan-only posture.

💬 Example Output

$ terraform output

xmatters_notification_id = "vu9U3hXa3q0AAAABABBkeG1hdHRlcnMtbm90aWZpY2F0aW9uAAA="
xmatters_notification_name = "xmatters-platform-team"

ℹ️ The module's own outputs are named id and name; a calling root module typically re-exposes them under a more descriptive name (as in examples/quickstart/main.tf), which is why the sample above shows xmatters_notification_id / xmatters_notification_name rather than the bare id / name.


🔍 Troubleshooting

Symptom Cause Fix
Authentication error at apply, even though the OAuth client has scopes granted Provisioned only a legacy API token, or granted account-scoped IAM policy scopes instead of settings:objects:* Confirm the OAuth client's IAM policy grants settings:objects:read/settings:objects:write (or the API token has settings.read/settings.write) — this is an environment-scoped Settings 2.0 resource, not an IAM/Account Management resource
Error: Invalid for_each argument referencing a sensitive value, if you modify main.tf Marking var.headers itself sensitive = true (not something this module does, but a common mistake when extending it) reintroduces Terraform's for-each-on-sensitive-value restriction Keep sensitive wrapped only around header.value.secret_value at the point of use, as authored — never mark the whole headers variable sensitive
terraform apply succeeds but xMatters never receives a notification even with active = true The referenced profile's alerting profile has no matching rules, or its rules' severity/tag scope doesn't cover the entities involved Confirm the dynatrace_alerting profile (owned by terraform-dynatrace-alerting-profile) has at least one rule whose scope covers the entities in question
A header's secret value shows up masked in Terraform plan output but still appears in plaintext in xMatters' own request logs This module's sensitive wrapping only affects Terraform's own plan/apply/state display — it does not control how the receiving xMatters endpoint logs inbound requests Review xMatters-side logging/retention settings for the webhook flow independently; Terraform-side redaction is not an end-to-end control
insecure = true was set as a quick fix for a TLS certificate error Common workaround habit — but this disables certificate validation for every subsequent notification call to that endpoint Fix the actual certificate chain at the xMatters endpoint; treat insecure = true as a reviewed, documented exception, never a default troubleshooting step
Duplicate-looking headers or a header silently overwritten Two entries in var.headers used the same map key (header name) Map keys must be unique by construction — Terraform rejects duplicate keys at parse time; rename one of the conflicting header entries

🔗 Related Docs

  • Terraform Registry — dynatrace-oss/dynatrace provider, resource dynatrace_xmatters_notification
  • Sibling module: terraform-dynatrace-alerting-profile (supplies profile)
  • This module's SCOPE.md

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

About

Terraform module: terraform-dynatrace-xmatters-notification

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages