Manages a single
dynatrace_pager_duty_notificationSettings 2.0 integration, routing Dynatrace problem notifications for one alerting profile to one PagerDuty service. Targetsdynatrace-oss/dynatrace ~> 1.98.
- π Wires a Dynatrace alerting profile to a PagerDuty service via the PagerDuty Events API
integration (
dynatrace_pager_duty_notification). - π Ships
active = falseby default β a newly-applied integration cannot page anyone until the caller explicitly reviews and opts in. - π Treats the PagerDuty Events API key as caller-supplied,
sensitive = truesecret material β never a literal default, never re-exported as a module output. - π§© Standalone primitive: one keystone resource (
this), no owned children, no nested block types in the live schema. The only cross-module surface is consuming an alerting profile'sid.
π‘ Why it matters: PagerDuty is frequently the terminal on-call escalation path for production incidents at a regulated financial institution. A misconfigured or prematurely active integration either pages the wrong on-call rotation or silently fails to page anyone at all. This module makes the safe state β inactive, no literal secret in state or config β the effortless default, and requires the caller to type extra characters to reach the paging-enabled state.
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 LR
AP["terraform-dynatrace-alerting-profile"]:::sibling
PD["terraform-dynatrace-pagerduty-notification"]:::this
KEY["dynatrace_pager_duty_notification"]:::keystone
EXT["PagerDuty (external service)"]:::sibling
AP -- "id -> profile" --> PD
PD -- "manages" --> KEY
KEY -- "routes events to" --> EXT
classDef this fill:#1496FF,color:#FFFFFF,stroke:#1496FF;
classDef keystone fill:#0A2540,color:#FFFFFF,stroke:#0A2540;
classDef sibling fill:#E5E7EB,color:#111111,stroke:#9CA3AF;
This module consumes exactly one sibling output β the id of a terraform-dynatrace-alerting-profile
instance, via var.profile β and manages exactly one resource. It does not own, create, or manage the
alerting profile itself, and PagerDuty's own account/service identifiers are external, PagerDuty-side
concepts with no corresponding Terraform state anywhere in this module family.
graph TD
subgraph Inputs
NAME["name (required)"]
PROFILE["profile (required)"]
ACCOUNT["account (required)"]
SERVICE["service (required)"]
APIKEY["api_key (sensitive, optional)"]
ACTIVE["active (optional, default false)"]
end
RES["dynatrace_pager_duty_notification.this"]:::keystone
NAME --> RES
PROFILE --> RES
ACCOUNT --> RES
SERVICE --> RES
APIKEY --> RES
ACTIVE --> RES
RES --> OUT_ID["output: id"]
RES --> OUT_NAME["output: name"]
classDef keystone fill:#0A2540,color:#FFFFFF,stroke:#0A2540;
Resource inventory:
dynatrace_pager_duty_notification.thisβ the sole resource. Flat attribute set (account,active,api_key,name,profile,service); no nestedblock_types, nofor_eachchildren.main.tfis a direct, total pass-through of every variable onto the resource β there is nothing here for adynamicblock to render.
| Item | Value |
|---|---|
| Terraform floor | >= 1.12.0 |
| Provider pin | dynatrace-oss/dynatrace ~> 1.98 |
| Provider block | None β the caller configures auth (OAuth client credentials, platform token, or legacy API token) and dt_env_url outside this module |
Schema notes that bite:
api_keyis provider-flaggedsensitive = trueand is genuinely optional in the live schema β there is no documented provider-side default. This module mirrors both facts: the variable is declaredsensitive = truewithdefault = null, andoutputs.tfomits any output derived fromapi_keyentirely β it is never emitted, sensitive-marked or otherwise.activeisrequiredat the resource level in the live schema (no provider-side default exists for it), but this module supplies its own module-level default offalseso that an omittedvar.activestill produces a fully validplan, with the resource always receiving an explicit value. This is a deliberate secure-by-default choice, not an artifact of the schema.profiletakes a raw string ID β this is a cross-module reference to aterraform-dynatrace-alerting-profileinstance'sidoutput, not a computed or nested object.- The live schema also exposes a provider-computed
legacy_idattribute (an interop cross-reference to the classic-API equivalent ID). This module deliberately excludes it from bothvariables.tfandoutputs.tfβ it is not a caller input, and this module does not add a passthrough output for it, consistent with the same judgment call made in the siblingterraform-dynatrace-alerting-profilemodule for its ownlegacy_idattribute ondynatrace_alerting. - No force-new/immutable fields were identified on this resource in the live schema at authoring time.
Verify against your pinned provider version before relying on that if the provider is upgraded past
~> 1.98β some immutability constraints have historically been added to this provider after being previously unenforced.
- 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 (recommended path per this module suite's 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, so no
account-idm-*orautomation:*scopes are needed for this module specifically.
- An OAuth client (or legacy API token) provisioned with the scopes/permissions above.
- An existing
terraform-dynatrace-alerting-profileinstance (or a pre-existing alerting profile ID) to pass intovar.profileβ this module has nothing to attach to otherwise. - A PagerDuty account (subdomain) and a PagerDuty service already provisioned on the PagerDuty side, plus a PagerDuty Events API integration key for that service if the integration is to actually deliver events. Provisioning the PagerDuty-side account/service/key is out of scope for this module and for the Dynatrace Terraform provider generally.
terraform-dynatrace-pagerduty-notification/
βββ providers.tf # required_version + provider pin β no provider {} block
βββ variables.tf # name, profile, account, service, api_key (sensitive), active (default false)
βββ main.tf # dynatrace_pager_duty_notification.this β thin, total renderer
βββ outputs.tf # id, name β api_key is intentionally never output
βββ SCOPE.md # Lightweight cross-module contract
βββ README.md # This file
βββ examples/
βββ quick-start/
βββ main.tf
module "pagerduty_notification" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-platform-oncall"
profile = module.alerting_profile.id # terraform-dynatrace-alerting-profile output
account = "acme"
service = "platform-oncall"
}The caller configures the dynatrace provider (auth and dt_env_url) once, outside this module β see
examples/quick-start/main.tf for a complete, directly-runnable snippet including provider
configuration. This minimal call deliberately omits api_key and active; the resulting integration
is created but inert (active = false) until reviewed.
Consumes
| Input | Type | Source module |
|---|---|---|
profile |
string, required |
terraform-dynatrace-alerting-profile (its id output) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
The Dynatrace Settings 2.0 object ID of this PagerDuty notification integration | Reference/introspection only β no sibling module in this domain consumes a notification-channel module's id as an input |
name |
The display name of this PagerDuty notification configuration | Documentation/reference only |
1 Β· Minimal inactive integration (secure-by-default)
module "pagerduty_notification" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-minimal"
profile = module.alerting_profile.id
account = "acme"
service = "default-oncall"
}π
api_keyandactiveare both omitted. The resulting integrationplans cleanly and is created in Dynatrace, but delivers nothing βactivedefaults tofalseand no PagerDuty Events API key is configured. This is the safe starting point for every new integration.
2 Β· Active integration targeting a specific PagerDuty service
variable "pagerduty_events_api_key" {
type = string
sensitive = true
}
module "pagerduty_notification_prod" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-prod-checkout-oncall"
profile = module.alerting_profile_prod.id
account = "acme"
service = "checkout-oncall"
api_key = var.pagerduty_events_api_key
active = true
}
β οΈ active = trueis an explicit opt-out of the secure default. Only set this once the account, service, API key, and alerting-profile scope have all been reviewed end to end β an active integration will page the moment a matching problem opens.
3 Β· Naming a PagerDuty integration per team on-call rotation
module "pagerduty_notification_payments_team" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-payments-team-oncall"
profile = module.alerting_profile_payments.id
account = "acme"
service = "payments-team-oncall"
}π‘
namehas no meaning to the PagerDuty side of the integration β it is purely the Dynatrace-side display name. Use a naming convention that ties the integration back to the owning team's on-call rotation for audit and troubleshooting clarity.
4 Β· Non-production PagerDuty account isolation
module "pagerduty_notification_nonprod" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-nonprod-smoke-test"
profile = module.alerting_profile_nonprod.id
account = "acme-nonprod"
service = "nonprod-smoke-test"
}βΉοΈ
accountis the PagerDuty subdomain (e.g.acme-nonprod.pagerduty.com), not a Dynatrace or Farm Credit internal identifier. Keep non-production and production PagerDuty accounts distinct so a non-production alert can never reach a production on-call rotation.
5 Β· Sourcing the API key from Azure Key Vault at the call site
data "azurerm_key_vault_secret" "pagerduty_events_api_key" {
name = "pagerduty-events-api-key-checkout"
key_vault_id = data.azurerm_key_vault.casey_secrets.id
}
module "pagerduty_notification_checkout" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-checkout-oncall"
profile = module.alerting_profile_checkout.id
account = "acme"
service = "checkout-oncall"
api_key = data.azurerm_key_vault_secret.pagerduty_events_api_key.value
active = true
}π Per this module suite's secure-by-default standard, this module never accepts a literal API key as a hardcoded value or a committed
.tfvarsentry. Source it from your own secrets manager at the call site β this module never logs, outputs, or re-exports it.
6 Β· Temporarily disabling an existing integration without destroying it
module "pagerduty_notification_maintenance_window" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-checkout-oncall"
profile = module.alerting_profile_checkout.id
account = "acme"
service = "checkout-oncall"
api_key = var.pagerduty_events_api_key
active = false # flipped from true during a planned maintenance window
}π‘ Flipping
activeback tofalseis a plan-only, in-place update β it does not recreate the resource. This is the intended pattern for a planned maintenance window, rather than removing the module block entirely.
7 Β· Fan-out: one alerting profile, several PagerDuty services via `for_each` at the call site
locals {
pagerduty_services = {
checkout = "checkout-oncall"
payments = "payments-oncall"
identity = "identity-oncall"
}
}
module "pagerduty_notification_by_service" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
for_each = local.pagerduty_services
name = "pagerduty-${each.key}-oncall"
profile = module.alerting_profile_platform.id
account = "acme"
service = each.value
}βΉοΈ This module has no owned children of its own, so fan-out to several PagerDuty services sharing one alerting profile is expressed with
for_eachat the call site, one module instance per service β not inside this module.
8 Β· Referencing a pre-existing (non-module-managed) alerting profile ID
module "pagerduty_notification_legacy_profile" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-legacy-profile-oncall"
profile = "b1cb2828-1234-4c5d-9e0a-abcdef012345" # a profile ID that exists in the environment but is not Terraform-managed here
account = "acme"
service = "legacy-oncall"
}
β οΈ This module does not validate that theprofileID actually exists β an invalid ID fails atapplytime against the live environment, not atplantime. Prefer wiring a module output over a literal ID whenever the profile is also managed by this Terraform configuration.
9 Β· Rotating the PagerDuty Events API key
variable "pagerduty_events_api_key" {
type = string
sensitive = true
# Supplied at plan/apply time from the caller's secrets manager β rotate the value there, then
# re-plan. Terraform redacts this value from console output because the variable itself is
# declared sensitive, and the underlying dynatrace_pager_duty_notification.api_key attribute is
# separately provider-flagged sensitive.
}
module "pagerduty_notification_checkout" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-checkout-oncall"
profile = module.alerting_profile_checkout.id
account = "acme"
service = "checkout-oncall"
api_key = var.pagerduty_events_api_key
active = true
}π Rotating the key is an in-place update to a
sensitiveattribute β it never appears in plan/apply console output. Confirm the new key was accepted by checking PagerDuty's own integration status afterapply, since this module's outputs never exposeapi_keyfor verification.
10 Β· Auditing team-keyed PagerDuty integrations by name convention
locals {
team_oncall_services = {
"payments-team" = "payments-oncall"
"identity-team" = "identity-oncall"
"platform-team" = "platform-oncall"
}
}
module "pagerduty_notification_team" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
for_each = local.team_oncall_services
name = "pagerduty-${each.key}"
profile = module.alerting_profile_platform.id
account = "acme"
service = each.value
}
output "pagerduty_integration_names_by_team" {
value = { for k, m in module.pagerduty_notification_team : k => m.name }
}π‘ A stable, human-readable
nameconvention (team + purpose) is the only audit trail this module exposes on the Dynatrace side β there is no tagging concept on this resource. Keep the convention consistent across environments.
11 Β· Environment-qualified naming for dev/test/prod parity
module "pagerduty_notification_env" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-${var.environment}-checkout-oncall"
profile = module.alerting_profile_env.id
account = var.environment == "prod" ? "acme" : "acme-nonprod"
service = "checkout-oncall"
active = var.environment == "prod"
}βΉοΈ Only the production environment sets
active = truehere; non-production environments keep the integration inert by default while still exercising the sameplanshape, which keeps environment parity without risking a non-production alert reaching a real on-call rotation.
12 Β· Zero-optional-input plan-only smoke test
module "pagerduty_notification_smoke_test" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-smoke-test"
profile = module.alerting_profile_smoke_test.id
account = "acme-nonprod"
service = "smoke-test"
}π§ͺ With only the four required inputs supplied,
terraform validateandterraform planboth succeed cleanly βapi_keydefers tonullandactivedefers to its module default offalse. This is the shape used in this module's own offline proof gate (see π§ͺ Testing below).
13 Β· ποΈ End-to-end composition β alerting profile β PagerDuty notification
This wires a terraform-dynatrace-alerting-profile instance's id output directly into this module's
profile input, the intended composition pattern for this domain.
module "alerting_profile_platform_oncall" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-alerting-profile.git?ref=v1.0.0"
name = "platform-oncall"
rules = {
availability-all-entities = {
severity_level = "AVAILABILITY"
include_mode = "INCLUDE_ANY"
delay_in_minutes = 5
tags = ["team:platform"]
}
}
}
variable "pagerduty_events_api_key" {
type = string
sensitive = true
}
module "pagerduty_notification_platform_oncall" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-pagerduty-notification.git?ref=v1.0.0"
name = "pagerduty-platform-oncall"
profile = module.alerting_profile_platform_oncall.id # <- wired from the sibling module's output
account = "acme"
service = "platform-oncall"
api_key = var.pagerduty_events_api_key
active = true
}
output "platform_oncall_alerting_profile_id" {
value = module.alerting_profile_platform_oncall.id
}
output "platform_oncall_pagerduty_notification_id" {
value = module.pagerduty_notification_platform_oncall.id
}π‘
terraform-dynatrace-alerting-profile'srulesmap defaults to{}(inert, per its own secure-by-default standard) β the rule shown here (INCLUDE_ANYon theteam:platformtag) is the caller's explicit choice to activate alerting for entities tagged for the platform team, narrowly scoped rather than a blanketNONE-scope match. Only once both the profile's rules and this module'sactiveflag are deliberately set does an end-to-end page reach PagerDuty.
| Variable | Type | Required | Default | Notes |
|---|---|---|---|---|
name |
string |
Yes | β | Dynatrace-side display name of the notification configuration |
profile |
string |
Yes | β | id of a terraform-dynatrace-alerting-profile instance (cross-module reference) |
account |
string |
Yes | β | PagerDuty account/subdomain |
service |
string |
Yes | β | PagerDuty service identifier (PagerDuty-side, not a Dynatrace resource) |
api_key |
string |
No | null |
PagerDuty Events API key; sensitive = true; source from your own secrets manager |
active |
bool |
No | false |
Secure-by-default: integration is inert until explicitly enabled |
Full variable schemas (verbatim from variables.tf)
variable "name" {
type = string
# The name of the notification configuration. Required β no provider-side default.
}
variable "profile" {
type = string
# The ID of the associated Dynatrace alerting profile. Required. Pass the `id` output of a
# terraform-dynatrace-alerting-profile instance β this module does not own, create, or manage
# alerting profiles; it only references one by ID.
}
variable "account" {
type = string
# The name of the PagerDuty account (the PagerDuty subdomain, e.g. "acme" for acme.pagerduty.com).
# Required β no provider-side default.
}
variable "service" {
type = string
# The name of the PagerDuty service this notification integration routes events to. Required β no
# provider-side default. A PagerDuty-side identifier, not a cross-module reference.
}
variable "api_key" {
type = string
default = null
sensitive = true
# The PagerDuty Events API key. Optional per the live schema; sensitive = true. Never pass a
# literal value β source it from your own secrets manager. Never output or re-exported by this
# module (see outputs.tf).
}
variable "active" {
type = bool
default = false
# Whether this integration is enabled. Required at the resource level in the live schema (no
# provider default); this module supplies its own secure-by-default module-level default of false.
}| Output | Description | Sensitive / Conditional |
|---|---|---|
id |
The Dynatrace Settings 2.0 object ID of this PagerDuty notification integration | No |
name |
The display name of this PagerDuty notification configuration | No |
api_key is never output β it is not present in outputs.tf at all, sensitive-marked or otherwise.
legacy_id (present in the live provider schema as a computed interop attribute) is likewise not
exposed by this module; see "Schema notes that bite" above.
main.tfis a single, non-dynamicresource block β the live schema fordynatrace_pager_duty_notificationhas no nestedblock_types, so there is nothing for adynamicblock to render. Every variable maps 1:1 onto a resource argument.api_key = try(var.api_key, null)inmain.tfis defensive rather than strictly necessary givenvar.api_key's owndefault = nullβ it guarantees the resource always receives an explicitnullrather than an unset argument if the variable's default is ever changed, consistent with the house "try(x, null)on every optional nested field" convention even though this field is not nested.- This module is environment-scoped (Settings 2.0, tied to a specific Dynatrace tenant), not account-scoped like the IAM/Account Management resource family β a single provider configuration targeting one environment is sufficient; there is no split-API-surface concern here the way there is for a composite mixing environment-scoped and IAM resources.
- There is no
for_each-driven child in this module. Fan-out to multiple PagerDuty services sharing one alerting profile is a call-site concern (see Example 7), not something this module renders internally. - Because
profileis a plain string, Terraform's implicit dependency graph (parent-before-child) relies entirely on the caller passingmodule.alerting_profile.idrather than a literal string, when the profile is also managed in the same configuration β passing a literal ID for an independently-managed profile is legitimate (see Example 8) but forfeits Terraform's automatic ordering guarantee for that profile.
| Concern | Safe default | Opt-out (caller must set explicitly) |
|---|---|---|
PagerDuty Events API key (api_key) |
Never a literal default; sensitive = true; never logged, output, or re-exported β caller sources it from their own secrets manager |
N/A β non-negotiable, not a default to opt out of |
Notification activation (active) |
Defaults to false β a newly-applied integration cannot fire before the caller reviews account/service/API key/profile scope |
Explicit active = true |
terraform init -backend=false
terraform validate
terraform fmt -check
Pin the module at ?ref=v1.0.0 β never a branch. This library is plan-only: a human applies from CI
after review. No cloud apply is ever performed by the authoring process itself.
terraform validate and terraform fmt -check are the offline proof gate for this module: they
confirm the HCL parses, the type system accepts every combination of required/optional inputs shown in
the Example Library, and formatting is house-standard. Both were run clean against this module
(terraform init -backend=false && terraform validate && terraform fmt -check, zero errors) before
this module was considered complete.
What offline validation does not cover, and what only a real plan against a live Dynatrace
environment exercises:
- Whether the
profileID actually resolves to an existing alerting profile in the target environment. - Whether the supplied
account(PagerDuty subdomain) andservicevalues correspond to a real, reachable PagerDuty account/service pair. - Whether the OAuth client or API token configured on the provider actually holds
settings:objects:read/settings:objects:write(orsettings.read/settings.write) β an under-scoped credential fails atapply, not atvalidate. - Whether a supplied
api_keyis a valid, active PagerDuty Events API key β Terraform has no way to verify PagerDuty-side credential validity offline.
$ terraform output
pagerduty_notification_id = "vu9U3hXY3q0AAAABACJk"
pagerduty_notification_name = "pagerduty-platform-oncall"
(Synthetic values for illustration β actual IDs are assigned by Dynatrace at apply time.)
| Symptom | Cause | Fix |
|---|---|---|
apply fails with an authentication/permission error even though the OAuth client "has scopes" |
The provider is configured with a legacy API token, but the token lacks settings.read/settings.write, or the OAuth client's IAM policy lacks settings:objects:read/settings:objects:write β this resource accepts either mechanism, so the failure mode depends on which one is actually configured |
Confirm which auth mechanism the provider is using (DT_CLIENT_ID/DT_CLIENT_SECRET/DT_ACCOUNT_ID vs. DYNATRACE_PLATFORM_TOKEN vs. DYNATRACE_API_TOKEN), then grant the matching scopes/permissions listed in π above |
| Integration exists in Dynatrace but never delivers a PagerDuty page | active is still false (the secure-by-default value), or api_key was never supplied |
Set active = true explicitly and supply a valid api_key, sourced from your secrets manager, once the configuration has been reviewed |
plan shows no error but PagerDuty never receives events even with active = true and api_key set |
account or service do not match a real PagerDuty account/service pair β Terraform cannot validate PagerDuty-side identifiers |
Verify the exact PagerDuty subdomain and service name in the PagerDuty UI; correct account/service and re-apply |
profile reference fails at apply with an object-not-found error |
The alerting profile ID passed to profile does not exist in the target environment (e.g. a literal ID left over from another environment) |
Prefer wiring module.alerting_profile.id over a literal string; if a literal ID is intentional (Example 8), confirm it against the target environment first |
Terraform plan shows api_key as (sensitive value) and a teammate asks "did the key even change?" |
Expected behavior β api_key is sensitive = true at both the variable and the underlying provider-schema attribute level |
Confirm the rotation succeeded by checking PagerDuty's own integration status after apply; this module's outputs never expose api_key for verification by design |
- Provider resource reference:
dynatrace_pager_duty_notification(dynatrace-oss/dynatrace ~> 1.98) - Sibling module:
terraform-dynatrace-alerting-profileβ owns theprofilethis module references - This module's
SCOPE.mdβ cross-module contract, required scopes, and prerequisites
π "Infrastructure as Code should be standardized, consistent, and secure."