Manages a direct-share grant for a single Dynatrace Platform document (dashboard, notebook, or launchpad) via
dynatrace_direct_shares, targeting thedynatrace-oss/dynatraceprovider~> 1.98. This is a composite module: one keystone resource per document-share grant, owning a caller-keyed collection of recipients rendered as a nested block inside that same resource.
- 🎯 Manages exactly one
dynatrace_direct_shares.thiskeystone resource per module instance — one document-share grant. - 🧷 Owns a caller-keyed
map(object({ id, type }))of recipients (var.recipients), rendered as a singledynamic "recipients"wrapper block containing a nesteddynamic "recipient"block — not a separate child resource. The underlying schema's ownrecipientswrapper isMax: 1; its innerrecipientset isMax: 1000. - 🚫 Produces an inert share (grants access to nobody) when
var.recipientsis left at its default empty map — the caller must explicitly add recipients to share the document with anyone. - 🔒 Defaults
accessto"read"— the narrower of the resource's two legal values — as an explicit house override; the provider itself documents no default for this field (see § Schema notes that bite). - 🔗 Consumes
document_idfrom a siblingterraform-dynatrace-documentinstance'sidoutput. This module does not create or manage the document itself, and does not create the recipient users/groups it references — both are owned elsewhere. - ☁️ Dynatrace SaaS only, and OAuth-only — this resource has no legacy API-token fallback (see § Required OAuth Scopes below), unlike most Settings 2.0 resources in this provider.
💡 Why it matters: direct shares are how an individual dashboard or notebook gets handed to a specific person or group without changing that document's environment-wide visibility. Modeling
accesswith a secure, explicit default and modelingrecipientsas a caller-keyed map (never a positional list) means a reviewer can see, line by line in aterraform plandiff, exactly who is gaining or losing access to exactly which document — and a newly-authored module call never accidentally grants write access or shares with an unintended recipient by omission.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
Doc["terraform-dynatrace-document<br/>(upstream module)"]:::sibling
Recip["IAM users / groups<br/>(pre-existing recipients)"]:::sibling
ThisMod["terraform-dynatrace-direct-shares<br/>(this module)"]:::thismodule
Keystone["dynatrace_direct_shares<br/>(keystone)"]:::keystone
Doc -- "emits `id`" --> ThisMod
ThisMod -- "sets `document_id` on" --> Keystone
Recip -- "referenced by `id` as" --> Keystone
classDef thismodule fill:#1496FF,color:#FFFFFF,stroke:#0A2540,stroke-width:2px;
classDef keystone fill:#0A2540,color:#FFFFFF,stroke:#0A2540,stroke-width:2px;
classDef sibling fill:#E8EEF3,color:#0A2540,stroke:#94A3B8,stroke-width:1px;
Validated via the Mermaid Chart MCP (validate_and_render_mermaid_diagram) — valid: true.
terraform-dynatrace-document(light neutral, upstream sibling) owns the targetdynatrace_documentthis module shares. Itsidoutput feeds this module'sdocument_idinput — an implicit cross-module reference, never a hardcoded document ID.- IAM users / groups (light neutral, external) are pre-existing entities this module references by
idinside eachrecipientsmap entry. This module does not create or manage users or groups. - This module (
#1496FF, white text) renders the one keystone resource,dynatrace_direct_shares.this(#0A2540), which is this domain's target/keystone.
flowchart TB
Inputs["var.document_id
var.access
var.recipients"]:::io
subgraph Keystone["dynatrace_direct_shares.this — keystone"]
direction TB
Attrs["document_id
access"]:::attr
subgraph RecipWrap["recipients (Block List, Max: 1) — owned, nested"]
direction TB
RecipItem["recipient (Block Set, Max: 1000)
id, type — one per var.recipients entry"]:::nested
end
end
Outputs["id
recipient_keys"]:::io
Inputs -- "renders into" --> Keystone
Keystone -- "emits" --> Outputs
classDef io fill:#E8EEF3,color:#0A2540,stroke:#94A3B8,stroke-width:1px;
classDef attr fill:#0A2540,color:#FFFFFF,stroke:#0A2540,stroke-width:1px;
classDef nested fill:#1496FF,color:#FFFFFF,stroke:#0A2540,stroke-width:2px;
Validated via the Mermaid Chart MCP (validate_and_render_mermaid_diagram) — valid: true.
Resource inventory:
| Resource | Count | Notes |
|---|---|---|
dynatrace_direct_shares.this |
1 (keystone, single instance per module call) | Top-level document_id and access attributes; owns a nested recipients wrapper (dynamic block, Max: 1 in the live schema) containing a nested recipient set (dynamic block, Max: 1000), driven by for_each = var.recipients. No separate child resource — the entire recipient collection lives inside this one resource. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
dynatrace-oss/dynatrace provider |
~> 1.98 |
provider {} block in this module |
None. The caller configures auth (OAuth client credentials — mandatory for this specific resource, see § Required OAuth Scopes) and dt_env_url once, outside this module. |
Schema notes that bite:
⚠️ Both closed-enum fields use lowercase values — confirmed directly against the live schema, and unusual for this provider.accessacceptsread/read-write, andrecipient.typeacceptsgroup/user— both lowercase, per the resource's own live schema documentation text ("Access grants. Possible values arereadandread-write"; "Type of the recipient. Possible values aregroup' anduser'"). Many other resources in this provider use uppercase enum conventions (e.g.dynatrace_mgmz_permission.permissions'sVIEWER,LOG_VIEWER, etc.) — do not "correct" this module's lowercasevalidation {}blocks to uppercase by analogy to sibling modules; the casing here is exact and confirmed, and this module'svariables.tfmatches it verbatim.- 🔒
access = "read"is an house secure default, not a provider default. The live schema documentsaccessas optional with no documented default value at all. Left genuinely unset at the provider level, the field's behavior is undefined by the schema. Per this module suite's secure-by-default posture ("the empty call must produce the safe resource"), this module fills that gap explicitly withdefault = "read"— the narrower of the two legal values — so a caller who calls this module without specifyingaccessgets a read-only share, never an unreviewed write grant. Setaccess = "read-write"explicitly to opt into broader access. - OAuth-only — no legacy API-token fallback. Unlike most Settings 2.0 resources in this provider
catalog,
dynatrace_direct_shares's own documentation names only the OAuth client-credentials mechanism (DT_CLIENT_ID/DT_CLIENT_SECRET/DT_ACCOUNT_ID); no API-token alternative is documented. See § Required OAuth Scopes below — do not assume the usual OAuth-or-API-token choice applies here. - Dynatrace SaaS only. The resource's own documentation states this explicitly; it has no Managed cluster equivalent.
recipientsis a single-instance wrapper, not itself repeatable. The live schema caps therecipientsblock atMax: 1— this module'smain.tfreflects that by guarding the wrapper'sdynamicblock withlength(var.recipients) > 0 ? [var.recipients]: [], producing zero or onerecipients { }blocks, never more than one. The repeatability lives one level down, in the nestedrecipientset (Max: 1000), which is whatvar.recipients' map entries actually drive.- No
ForceNew/immutability is called out anywhere on this resource's documented schema page — the doc page does not flagdocument_id,access, orrecipientsas immutable. Treat this as "not confirmed either way from the documentation," not as confirmed mutability; verify against the raw JSON schema (terraform providers schema -json) or a liveplanif immutable-field behavior matters for a specific change (e.g. re-pointingdocument_idat a different document). - This resource is excluded by default from the provider's own export utility — per its
documentation,
terraform-provider-dynatrace -export dynatrace_direct_sharesmust name the resource explicitly to retrieve existing configuration. Not relevant to authoring this module (export tooling is out of scope for this module), but worth knowing if reconciling against a live environment later.
Confirmed directly against the live provider schema (provider
dynatrace-oss/dynatrace v1.100.0). The resource's own documentation states, verbatim:
Dynatrace SaaS only.
To utilize this resource, please define the environment variables
DT_CLIENT_ID,DT_CLIENT_SECRET,DT_ACCOUNT_IDwith an OAuth client including the following permissions: Read direct-shares (document:direct-shares:read), Write direct-shares (document:direct-shares:write), and Delete direct-shares (document:direct-shares:delete).
This resource is OAuth-only — a legacy API token is not accepted at all. Unlike most Settings 2.0 resources in this provider (which typically accept either OAuth or a legacy API token), this resource's own schema documentation names only the OAuth client-credentials mechanism, consistent with this module suite's auth-model conventions for Platform/Automation-family resources: this resource backs a Grail-native Document, part of the platform Document Service, not a classic environment-scoped Settings 2.0 object.
- OAuth (required — mandatory, not merely preferred): grant the OAuth client's IAM policy
document:direct-shares:read,document:direct-shares:write, anddocument:direct-shares:delete. - Platform token: presumed viable as OAuth's long-lived equivalent (same scope-grant model, mutually exclusive with OAuth client credentials at the provider-config level, per this module suite's authentication model) — but this is not separately confirmed in the resource's own documentation page, which names only the OAuth-client-credentials env vars. Verify against your environment before relying on it.
- Legacy API token: not accepted. Do not provision a legacy API token as this module's sole auth path — it will not authenticate this resource, and the failure will present as an opaque authentication error unrelated to the token's actual permissions (see § Troubleshooting).
- This module does not touch any other IAM or Automation resource, so no broader
account-idm-*/automation:*scopes are needed for this module specifically.
- Target environment is Dynatrace SaaS — this resource has no Managed-cluster equivalent.
- The target document must already exist — either created out of band or in the same plan via an
implicit reference to
terraform-dynatrace-document'sidoutput. - The target recipients (users/groups) must already exist. This module does not create them; the
recipients[*].idfield is a plain identifier reference, not a name that resolves automatically. - An OAuth client (
DT_CLIENT_ID/DT_CLIENT_SECRET/DT_ACCOUNT_ID) provisioned withdocument:direct-shares:read,document:direct-shares:write, anddocument:direct-shares:delete, bound via an IAM policy. A legacy API token alone is not sufficient for this module.
terraform-dynatrace-direct-shares/
├── providers.tf # required_version + dynatrace-oss/dynatrace ~> 1.98 pin — no provider {} block
├── variables.tf # document_id, access (default "read"), recipients (default {})
├── main.tf # dynatrace_direct_shares.this, dynamic "recipients" wrapping dynamic "recipient"
├── outputs.tf # id, recipient_keys
├── SCOPE.md # cross-module Consumes/Emits contract + confirmed auth model
├── examples/ # runnable Quick Start snippet(s)
│ └── quickstart/main.tf
└── README.md # this file
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0001-4a2b-8c3d-000000000001" # terraform-dynatrace-document's `id` output
recipients = {
finance_readers = {
id = "7f6e9c2a-1111-4a5b-9c3d-000000000001"
type = "group"
}
}
# access omitted — defaults to "read", the house secure default
}ℹ️ The caller configures the
dynatraceprovider (OAuth client credentials — mandatory for this resource, see § Required OAuth Scopes above — plusdt_env_url) once, outside this module. This module never declares aprovider {}block or an auth-related variable.
A full standalone, directly-runnable version of this snippet (with its own terraform/provider block)
is in examples/quickstart/main.tf.
Consumes
| Input | Type | Source module |
|---|---|---|
document_id |
string, required |
terraform-dynatrace-document (its id output) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Direct-share grant ID | Reference only — no sibling module in this catalog consumes this by id as of this writing |
recipient_keys |
The caller-chosen keys of the configured recipients map |
Reference only |
1 · Minimal single-recipient share — read access, the secure default
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0001-4a2b-8c3d-000000000001"
recipients = {
jane_doe = {
id = "1a2b3c4d-0000-4a2b-8c3d-000000000010"
type = "user"
}
}
}💡 The smallest real call: one document, one recipient,
accessleft at its"read"default.
2 · Read-write share — explicit broader-access opt-out
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0002-4a2b-8c3d-000000000002"
access = "read-write"
recipients = {
dashboard_editor = {
id = "2b3c4d5e-0001-4a2b-8c3d-000000000011"
type = "user"
}
}
}
⚠️ access = "read-write"grants the recipient edit rights on the shared document. There is no module default that grants this silently — the caller must set it explicitly.
3 · Group-recipient share
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0003-4a2b-8c3d-000000000003"
recipients = {
platform_engineering = {
id = "3c4d5e6f-0001-4a2b-8c3d-000000000012"
type = "group"
}
}
}ℹ️
type = "group"— confirmed lowercase per the live schema. Grants the entire groupreadaccess (the default) to this one document.
4 · Multi-recipient at scale — 6 recipients mixing users and groups via for_each
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0004-4a2b-8c3d-000000000004"
access = "read"
recipients = {
finance_group = { id = "4d5e6f70-0001-4a2b-8c3d-000000000013", type = "group" }
risk_group = { id = "5e6f7081-0002-4a2b-8c3d-000000000014", type = "group" }
audit_group = { id = "6f708192-0003-4a2b-8c3d-000000000015", type = "group" }
jane_doe = { id = "708192a3-0004-4a2b-8c3d-000000000016", type = "user" }
john_smith = { id = "8192a3b4-0005-4a2b-8c3d-000000000017", type = "user" }
service_reporter = { id = "92a3b4c5-0006-4a2b-8c3d-000000000018", type = "user" }
}
}💡 Six independent recipients in one grant, each keyed by role. Removing
service_reporterlater touches only that map entry's plan —for_each(never a positional list) keys each nestedrecipientblock by the caller's own map key, so unrelated recipients are untouched.
5 · Access-level variant — same document, read vs. read-write compared side by side
module "direct_share_viewers" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0005-4a2b-8c3d-000000000005"
access = "read"
recipients = {
ops_readers = { id = "a3b4c5d6-0001-4a2b-8c3d-000000000019", type = "group" }
}
}
module "direct_share_editors" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0005-4a2b-8c3d-000000000005" # same document
access = "read-write"
recipients = {
ops_editors = { id = "b4c5d6e7-0002-4a2b-8c3d-00000000001a", type = "group" }
}
}🔒 Two separate
dynatrace_direct_sharesgrants against the same document, deliberately split by access level — a common segregation-of-duties shape where a smaller editor group getsread-writeand a broader readership getsread, rather than one grant with mixed access.
6 · Empty recipients — secure-by-default inert share
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0006-4a2b-8c3d-000000000006"
# recipients intentionally omitted — defaults to {}
}🔒
var.recipientsdefaults to{}. The resultingdynatrace_direct_shares.thisresource still exists (withaccess = "read"), but grants access to nobody until the caller populates at least one recipient — the safe, inert default.
7 · Single-user recipient only
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0007-4a2b-8c3d-000000000007"
recipients = {
on_call_engineer = {
id = "c5d6e7f8-0001-4a2b-8c3d-00000000001b"
type = "user"
}
}
}ℹ️ A single named individual, rather than a group — useful for a one-off share ahead of an incident review or audit, without changing the document's broader visibility.
8 · Explicit mixed-type pair — one user, one group, in a single grant
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0008-4a2b-8c3d-000000000008"
recipients = {
sample_service_user = { id = "d6e7f809-0001-4a2b-8c3d-00000000001c", type = "user" }
sample_group = { id = "e7f8091a-0002-4a2b-8c3d-00000000001d", type = "group" }
}
}💡 Mirrors the provider's own documented resource example (a service user plus a group in one
dynatrace_direct_sharesgrant), rendered through this module's caller-keyed map instead of two positionalrecipient { }blocks.
9 · Recipient with omitted type — type left unset
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-0009-4a2b-8c3d-000000000009"
recipients = {
unclassified_recipient = {
id = "f8091a2b-0001-4a2b-8c3d-00000000001e"
# type intentionally omitted — optional per the live schema
}
}
}ℹ️
typeis optional on the underlyingrecipientblock; this module'smain.tfrenderstry(recipient.value.type, null), so an omittedtypepassesnullthrough to the provider rather than failing atplan. Confirm with your environment whether the platform can resolve an untyped recipient id before relying on this in a production grant.
10 · Locals-driven for_each construction — DRY at scale
locals {
finance_group_ids = {
finance_readers = "091a2b3c-0001-4a2b-8c3d-00000000001f"
finance_leads = "1a2b3c4d-0002-4a2b-8c3d-000000000020"
finance_auditors = "2b3c4d5e-0003-4a2b-8c3d-000000000021"
}
}
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-000a-4a2b-8c3d-00000000000a"
recipients = {
for role, group_id in local.finance_group_ids : role => {
id = group_id
type = "group"
}
}
}ℹ️ Building
recipientsfrom aforexpression over alocalsmap keeps a repetitive, same-shape recipient list DRY while still producing a stable, caller-keyed map forfor_each— the resulting keys are stillrole, not a numeric index.
11 · Multiple documents, one share pattern each — caller-level for_each over module calls
locals {
shared_documents = {
exec_dashboard = "d3f4a5b6-000b-4a2b-8c3d-00000000000b"
incident_runbook = "d3f4a5b6-000c-4a2b-8c3d-00000000000c"
}
}
module "direct_share" {
for_each = local.shared_documents
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = each.value
recipients = {
leadership_group = {
id = "3c4d5e6f-0001-4a2b-8c3d-00000000000d"
type = "group"
}
}
}💡 This module itself has one keystone per instance, not a top-level
for_eachover documents — the caller supplies the fan-out across documents by puttingfor_eachon themoduleblock itself, onedynatrace_direct_shares.thisper document.
12 · Segregation of duties — narrow read grant reviewed separately from a write grant
module "direct_share_read_only" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-000d-4a2b-8c3d-00000000000e"
access = "read"
recipients = {
broad_readership = { id = "4d5e6f70-0001-4a2b-8c3d-00000000000f", type = "group" }
}
}🔒 Keeping the read-only grant in its own module call (rather than merging it with a hypothetical write grant) keeps each grant's
terraform plandiff scoped to one access level and one recipient set — easier for a reviewer to reason about during a periodic access review.
13 · Narrow single-group read-write grant for a small maintenance team
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = "d3f4a5b6-000e-4a2b-8c3d-00000000000f"
access = "read-write"
recipients = {
dashboard_maintainers = {
id = "5e6f7081-0001-4a2b-8c3d-000000000010"
type = "group"
}
}
}
⚠️ Reserveread-writefor a genuinely small, named group responsible for maintaining this specific document — not a general operations or platform-wide group.
14 · Custom document + narrow scoped share, wired via explicit local
locals {
quarterly_review_document_id = "d3f4a5b6-000f-4a2b-8c3d-000000000011"
}
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = local.quarterly_review_document_id
access = "read"
recipients = {
board_reviewers = {
id = "6f708192-0001-4a2b-8c3d-000000000012"
type = "group"
}
}
}ℹ️ A named
localfor the document id (rather than a bare literal) makes the intent — "this share targets the quarterly review document" — legible in the plan without requiring a lookup elsewhere.
15 · 🏗️ End-to-end composition — document + direct share
module "document" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-document.git?ref=v1.0.0"
name = "Payments Platform Reliability Overview"
type = "dashboard"
content = jsonencode({
"version" : 1,
"variables" : [],
"tiles" : {
"0" : {
"type" : "markdown",
"title" : "",
"content" : "Payments platform reliability overview"
}
},
"layouts" : {
"0" : { "x" : 0, "y" : 0, "w" : 24, "h" : 14 }
}
})
# private left at its default (true) -- visible only to the document's owner until shared below
}
module "direct_share" {
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"
document_id = module.document.id # implicit reference -- terraform-dynatrace-document's `id` output
recipients = {
payments_platform_viewers = {
id = "7f6e9c2a-1111-4a5b-9c3d-000000000001"
type = "group"
}
payments_platform_owner = {
id = "8091a2b3-2222-4a5b-9c3d-000000000002"
type = "user"
}
}
# access omitted -- defaults to "read"
}
output "document_id" {
value = module.document.id
}
output "direct_share_id" {
value = module.direct_share.id
}💡 This is the module's mandatory end-to-end shape:
terraform-dynatrace-documentowns the document and emitsid;terraform-dynatrace-direct-sharesconsumes thatidasdocument_idand owns the recipient collection for that one document. Terraform's implicit dependency graph orders the document before the share automatically — nodepends_onis needed or used.
| Variable | Type | Default | Required |
|---|---|---|---|
document_id |
string |
— | Yes |
access |
string ("read" | "read-write") |
"read" (house secure default) |
No |
recipients |
map(object({ id = string, type = optional(string) })) |
{} |
No (empty map shares with nobody) |
Full object schemas
variable "document_id" {
type = string
}
variable "access" {
type = string
default = "read"
validation {
condition = contains(["read", "read-write"], var.access)
error_message = "var.access must be one of: \"read\", \"read-write\"."
}
}
variable "recipients" {
type = map(object({
id = string
type = optional(string)
}))
default = {}
validation {
condition = alltrue([
for k, v in var.recipients :
v.type == null || contains(["group", "user"], v.type)
])
error_message = "Each recipients[*].type, when set, must be one of: \"group\", \"user\" (lowercase, per the live schema)."
}
}Per-field notes:
| Field | Type | Notes |
|---|---|---|
document_id |
string |
Required. The target document's id. Source from terraform-dynatrace-document's id output; never hardcode. |
access |
string |
Optional. Closed enum read / read-write (lowercase, confirmed against the live schema). No provider-documented default; this module's default = "read" is an house secure-default override. |
recipients |
map(object({ id = string, type = optional(string) })) |
Optional. Default {} — an inert grant with no recipients. Each entry's id is the recipient user/group's own id; type, when set, must be group or user (lowercase, confirmed against the live schema). |
| Output | Description | Sensitive |
|---|---|---|
id |
Direct-share grant ID (dynatrace_direct_shares.this.id) |
No |
recipient_keys |
The caller-chosen keys of the configured recipients map (keys(var.recipients)) |
No |
- One keystone, no separate child resource.
main.tfrenders exactly one resource,dynatrace_direct_shares.this. The entire recipient collection lives inside that same resource as a nesteddynamic "recipients"block wrapping a nesteddynamic "recipient"block — there is no second resource type in this module, unlike composite modules whose children are independently addressable resources. - The
recipientswrapper is guarded to render zero or one blocks, never more.main.tfusesfor_each = length(var.recipients) > 0 ? [var.recipients]: []on the outerdynamic "recipients"block specifically because the live schema caps that wrapper atMax: 1— this is not an arbitrary stylistic choice, it mirrors a real schema constraint. for_eachkey stability for the innerrecipientset. Eachrecipientblock is driven byfor_each = recipients.value(the caller'svar.recipientsmap), so removing one recipient fromvar.recipientsnever re-plans any other recipient's block — consistent with the house rule of never usingcountfor a collection of relationship-shaped records.try(recipient.value.type, null)letstypebe genuinely optional. Arecipientsmap entry that omitstypestill plans cleanly; the provider receivesnullfor that attribute rather than the module failing atplantime.- No secrets, no
sensitive = truefields.document_id,access, and everyrecipients[*]field are plain identifiers/enum values, not credential material — the house secure-by-default credential-handling pattern does not apply to any field in this specific module. - OAuth-only auth surface. Unlike most composite modules in this catalog that document an OAuth-or-API-token choice, this module's underlying resource accepts OAuth client credentials only (see § Required OAuth Scopes) — there is no fallback path to document or code around.
| Concern | Safe default in this module | Opt-out (caller must set explicitly) |
|---|---|---|
access |
Defaults to "read" — an house override of the provider's undocumented default, per this module suite's secure-by-default posture |
Caller sets access = "read-write" explicitly |
recipients |
Defaults to {} — an inert share with no recipients configured |
Caller must populate at least one map entry to grant access to anyone |
for_each vs. positional lists |
Every recipient is keyed by the caller's own map key, never a numeric index | N/A — the module never exposes an index-based collection to opt into |
| Closed-enum validation | Both access and recipients[*].type are checked at terraform validate/plan against their exact, confirmed-lowercase legal values via this module's own validation {} blocks |
N/A — not a default to opt out of; this is a correctness guard, not a policy choice |
| Auth mechanism | This module's permissions section documents OAuth as the only accepted mechanism for this specific resource — no ambiguity about a fallback that does not exist | N/A — provider/resource-level constraint, not a module-configurable default |
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module source to an explicit tag — never a branch:
source = "git::https://github.com/microsoftexpert/terraform-dynatrace-direct-shares.git?ref=v1.0.0"This library is plan-only. No terraform apply is run as part of authoring or reviewing this module —
a human runs apply from CI after reviewing the plan.
What validate/fmt cover offline, without any live Dynatrace environment:
document_id,access, and everyrecipients[*]entry's shape matchvariables.tf's type — a missingdocument_id, or a mistypedrecipientsentry (e.g. a missingid), fails atvalidate, notapply.accessandrecipients[*].typeare checked against their closed, confirmed-lowercase enum values via this module's ownvalidation {}blocks — an unsupported value (e.g."READ"or"Group") fails atvalidate/plan, not as an opaque API-side error against a live tenant.fmt -checkenforces canonical HCL formatting.
What only a real plan against a live environment can additionally catch — this module's validate
cannot verify these:
- Whether
document_idactually corresponds to an existing document in the target environment. - Whether each
recipients[*].idactually corresponds to an existing user or group. - Whether the configured OAuth client actually holds
document:direct-shares:read/write/delete. - Whether the target environment is genuinely SaaS (this resource has no Managed equivalent, and a
Managed-targeted apply will fail at the API, not at
plan).
Representative terraform output for Example 15 (end-to-end composition):
direct_share_id = "9a1c4e70-aaaa-4b6c-9d4e-000000000099"
document_id = "d3f4a5b6-bbbb-4a2b-8c3d-000000000098"
| Symptom | Cause | Fix |
|---|---|---|
apply fails with an authentication error even though a legacy API token is configured |
This resource is OAuth-only — a legacy API token does not authenticate dynatrace_direct_shares at all |
Provision an OAuth client (DT_CLIENT_ID/DT_CLIENT_SECRET/DT_ACCOUNT_ID) scoped document:direct-shares:read, document:direct-shares:write, document:direct-shares:delete; a token alone will not work regardless of the permissions granted to it |
terraform validate rejects access even though the value "looks right" |
The value was cased like "Read" or "READ" instead of the confirmed-lowercase "read" |
Use the exact lowercase legal values read / read-write |
terraform validate rejects a recipients[*].type entry |
The value was cased like "Group"/"User" instead of the confirmed-lowercase "group"/"user" |
Use the exact lowercase legal values group / user |
| A direct share applies but the recipient reports no visible access change | recipients[*].id was populated with a display name instead of the recipient's actual user/group id |
Replace the value with the recipient's real id, not a human-readable name |
apply fails with "resource not found" for the document |
document_id does not correspond to an existing document, or the document was created in a different environment than the provider is configured against |
Confirm document_id was sourced from terraform-dynatrace-document's id output in the same plan/environment, not a hardcoded value from another environment |
| A share was applied with broader access than intended | access was set to "read-write" explicitly by a prior change without review |
There is no accidental default to broader access in this module — review the specific access value in the offending module call; the default is always "read" unless explicitly overridden |
| Recipient count in the plan looks wrong after adding/removing one entry | An unrelated recipients map entry appears to change alongside the intended edit |
Confirm var.recipients uses stable map keys, not a for expression that regenerates numeric-like keys; for_each keys must stay stable across plans for unrelated entries to remain untouched |
apply succeeds against a Dynatrace Managed cluster but the resource behaves unexpectedly (or provisioning fails entirely) |
This resource is documented as Dynatrace SaaS only | Confirm the target environment is SaaS before using this module; there is no Managed equivalent for dynatrace_direct_shares |
- Dynatrace provider registry docs —
dynatrace_direct_shares(verify current argument reference and authentication support against the live schema for your pinned provider version, not this README alone). - Dynatrace provider registry docs —
dynatrace_document(source of theidattribute this module depends on). - Sibling module:
terraform-dynatrace-document— owns the target document; see itsoutputs.tffor theidoutput this module'sdocument_idinput consumes. - This module's own
SCOPE.md— the authoritative Consumes/Emits contract this README is derived from, including the confirmed OAuth-only auth model.
💙 "Infrastructure as Code should be standardized, consistent, and secure."

