Skip to content

microsoftexpert/tf-mod-grafana-data-source

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“Š Grafana Data Source Terraform Module

Manages a single grafana_data_source β€” connection, auth mode, and JSON-encoded configuration β€” as one standalone keystone resource. Targets grafana/grafana (~> 4.0).


Badges

Terraform Provider Module Version Module Type Resources Posture


🧩 Overview

  • πŸ”Œ Creates and manages a grafana_data_source β€” name/type required; connection URL, access mode, basic auth, database/username, org scope, Private Data Source Connect network ID, and custom uid all optional per the live v4.40.1 schema.
  • 🧬 Renders json_data_encoded and secure_json_data_encoded β€” both plain serialized-JSON strings in the live schema β€” from this module's own typed inputs, rather than accepting raw JSON strings from the caller.
  • πŸ”’ Forces two secure defaults, both non-negotiable per this suite's secure-by-default convention: basic_auth_enabled = false and json_data.tlsSkipVerify = false (TLS certificate verification stays on). A validation {} block blocks a caller from smuggling a tlsSkipVerify override into the open extra json_data map.
  • πŸ•΅οΈ Isolates every credential-shaped input β€” secure_json_data, http_headers β€” into its own sensitive = true variable, and never re-exposes either in any output.
  • πŸ†” Emits uid as the primary output β€” the identity the provider's own Import syntax uses, and the identity any downstream dashboard, alert rule, or query configuration references this data source by.

πŸ’‘ Why it matters: a data source's TLS posture and auth mode are security-relevant properties in a regulated environment. Making basic_auth_enabled and tlsSkipVerify explicit, secure-by-default typed fields β€” rather than leaving them buried inside a caller-supplied raw JSON blob β€” means a terraform plan diff surfaces any change to either posture in plain sight, and a reviewer can block a pull request that flips one without ever having to parse embedded JSON.


❀️ 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 TD
 org["tf-mod-grafana-organization\n(org scope)"]:::sibling
 datasource["tf-mod-grafana-data-source"]:::this
 dashboard["tf-mod-grafana-dashboard\n(panel/query datasource refs)"]:::sibling
 rulegroup["tf-mod-grafana-alerting-rule-group\n(alert query datasource refs)"]:::sibling

 org -- "org_id (tostring)" --> datasource
 datasource -- "uid (datasource reference)" --> dashboard
 datasource -- "uid (datasource reference)" --> rulegroup

 classDef this fill:#F46800,color:#FFFFFF,stroke:#F46800;
 classDef sibling fill:#E8E8E8,color:#111217,stroke:#8A8A8A;
Loading

Validated via the Mermaid Chart MCP (valid: true) before embedding. tf-mod-grafana-organization feeds an optional org_id scope into this module; this module's own uid output feeds two content/alerting- layer siblings that query this data source by UID reference. Neither tf-mod-grafana-dashboard nor tf-mod-grafana-alerting-rule-group is fully authored as of this session β€” treat those edges as the intended, documented contract, not as independently verified against a live plan.


🧬 What this builds

flowchart LR
 subgraph inputs["Inputs"]
 vards["var.data_source\n(object)"]
 varjson["var.json_data\n(object, tlsSkipVerify default false)"]
 varsecure["var.secure_json_data\n(map(string), sensitive)"]
 varheaders["var.http_headers\n(map(string), sensitive)"]
 end

 keystone["grafana_data_source.this\n(keystone)"]:::this

 vards -- "name, type, url, access_mode, basic_auth_enabled,..." --> keystone
 varjson -- "json_data_encoded (jsonencode)" --> keystone
 varsecure -- "secure_json_data_encoded (jsonencode)" --> keystone
 varheaders -- "http_headers" --> keystone

 keystone --> outuid["uid\n(primary_output)"]
 keystone --> outid["id"]
 keystone --> outname["name"]
 keystone --> outtype["type"]
 keystone --> outurl["url"]
 keystone --> outisdefault["is_default"]

 classDef this fill:#F46800,color:#FFFFFF,stroke:#F46800;
Loading

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

Resource inventory:

Resource Cardinality Role
grafana_data_source.this 1 (keystone) The data source itself: name, type, connection details, auth mode, JSON-encoded config, secrets, custom HTTP headers

βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
grafana/grafana provider ~> 4.0 (confirmed latest at authoring time: 4.40.1, installed and locked during this session's terraform init)
Provider block None β€” the caller configures provider "grafana" {... } (with whichever alias this module's auth mechanism requires) at the root module and passes it in via providers = { grafana =... }

Schema notes that bite:

  • json_data_encoded and secure_json_data_encoded are plain serialized-JSON Strings in the live v4.40.1 schema ("Serialized JSON string containing the json data..." / "...containing the secure json data..."), not maps or nested objects. The REQUIRED arguments for the resource as a whole "vary depending on the type of data source selected" per the provider's own top-of-page description β€” this module cannot enumerate every per-type key without becoming as dynamic as the field itself, so only tlsSkipVerify is typed explicitly (forced to a secure default); everything else flows through an open extra map (see Β§15 Architecture Notes).
  • http_headers is schema-marked (Map of String, Sensitive) by the provider itself β€” this module mirrors that by marking its own var.http_headers sensitive = true.
  • uid is Optional with no separate "Computed" annotation surfaced in the rendered docs page, but its own field description ("If unset, this will be automatically generated") and the resource's documented Import syntax (terraform import grafana_data_source.name "{{ uid }}") together confirm it behaves as Optional+Computed, exactly like this library's grafana_folder.uid. Treat as immutable-after-set; this was not independently re-verified as force-new this session (no explicit RequiresReplace annotation is surfaced in the rendered schema page) β€” flagged as an inference from the Import syntax and the grafana_folder.uid precedent, not a schema-confirmed fact.
  • grafana_data_source.id is a String ("The ID of this resource") β€” informational only; the provider's own Import syntax uses uid, not id, reinforcing uid as the resource's real cross-session identity.
  • private_data_source_connect_network_id's own field description states it "Can only be used with data sources in Grafana Cloud" β€” see Β§8 Grafana Prerequisites. This is a Cloud-vs-OSS seam this suite's design conventions call out as a recurring pattern for this provider.
  • access_mode defaults to "proxy" per the live schema's own field description; this module mirrors that default explicitly in variables.tf rather than leaving it to the provider alone, consistent with this suite's "make the type the contract" governing idea.
  • basic_auth_username, database_name, and username are each documented as "(Required by some data source types)" β€” the live schema does not encode a mechanism to make a field's requiredness conditional on type, and neither does this module; a caller supplying a mismatched shape for a given type will discover it only at apply time against a live Grafana instance, not at plan time.

πŸ”‘ Required Grafana Auth & Scope

Grafana core auth (see this suite's Authentication model, "Grafana core auth" table) β€” the auth provider argument (Service Account token). Data source management is an org-admin-gated capability: least-privilege convention for this module is an Admin-scoped Service Account token β€” Editor-level tokens are not sufficient to create or modify data sources on most Grafana instances (a platform-side, not a Terraform-provider-side, permission gate). Configured once by the caller on whichever aliased grafana provider instance is passed into this module via providers = { grafana =... }; this module declares no alias or credential of its own.

Grafana Prerequisites

  • private_data_source_connect_network_id is usable only with data sources hosted in Grafana Cloud β€” do not set this field against a self-managed OSS/Enterprise instance.
  • The required field set for grafana_data_source varies entirely by data_source.type (e.g. url + basic_auth_username for influxdb; json_data.extra.defaultRegion + secure_json_data.accessKey/ secretKey for cloudwatch). Confirm the specific data source type's required shape against the provider's own per-type documentation, or by inspecting the network payload Grafana's UI sends when saving that data source type manually, before relying on this module for a new type.
  • No feature flag or minimum Grafana version gates the core grafana_data_source resource itself.

πŸ“ Module Structure

tf-mod-grafana-data-source/
β”œβ”€β”€ providers.tf # required_providers: grafana/grafana ~> 4.0, required_version >= 1.12.0. No provider {} block.
β”œβ”€β”€ variables.tf # var.data_source (keystone object) + var.json_data / var.secure_json_data / var.http_headers
β”œβ”€β”€ main.tf # grafana_data_source.this (keystone, no children)
β”œβ”€β”€ outputs.tf # uid (primary), id, name, type, url, is_default
β”œβ”€β”€ README.md # this file
β”œβ”€β”€ SCOPE.md # lightweight cross-module contract, auth/scope, prerequisites, provider gotchas
└── examples/ # runnable example(s)

βš™οΈ Quick Start

module "prometheus_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "prometheus-primary"
    type = "prometheus"
    url  = "https://prometheus.internal.financialpartners.com"
  }
}

The caller configures the grafana provider (with the alias this module's Admin-scoped Service Account token auth requires) once at the root module β€” this module never accepts its own url, auth, or org_id default. The empty-call default here already carries this module's secure posture: no basic auth, TLS verification on (tlsSkipVerify = false).


πŸ”Œ Cross-Module Contract

Consumes

Input Type Source module
org_id (optional) string tf-mod-grafana-organization's org_id primary output, wrapped in tostring(...) (it is a Number)
private_data_source_connect_network_id (optional) string No owning sibling module in this library's current catalog β€” caller-supplied ID, Grafana-Cloud-only

Emits

Output Description Consumed by
uid Primary output. Data source's stable UID; also the identity the provider's own Import syntax uses tf-mod-grafana-dashboard (panel/query datasource references), tf-mod-grafana-alerting-rule-group (alert query datasource references) β€” both by reference; neither module is fully authored as of this session
id Provider's own resource id (String) Informational only β€” prefer uid
name Pass-through of configured name Informational
type Pass-through of configured type keyword Informational
url Pass-through of configured url, where set Informational
is_default Whether this data source is the org-wide default Informational β€” uniqueness across instances not enforced by this module

πŸ“š Example Library

1 Β· Minimal Prometheus data source (secure default)
module "prometheus_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "prometheus-primary"
    type = "prometheus"
    url  = "https://prometheus.internal.financialpartners.com"
  }
}

πŸ’‘ The empty-call default: basic_auth_enabled = false, json_data.tlsSkipVerify = false (TLS verification stays on), no custom uid (Grafana generates one), no secrets.

2 Β· Prometheus with basic auth (secure variant β€” password isolated)
module "mimir_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name                = "mimir"
    type                = "prometheus"
    url                 = "https://my-instances.example.net"
    basic_auth_enabled  = true
    basic_auth_username = "svc-mimir-reader"
  }

  json_data = {
    extra = {
      httpMethod        = "POST"
      prometheusType    = "Mimir"
      prometheusVersion = "2.4.0"
    }
  }

  secure_json_data = {
    basicAuthPassword = var.mimir_basic_auth_password
  }
}

πŸ”’ basic_auth_enabled = true is an explicit caller opt-in (the module default is false). basicAuthPassword flows through var.secure_json_data, sensitive = true, never through a plain-text variable. TLS verification is untouched here β€” tlsSkipVerify still defaults false.

3 Β· Loki data source
module "loki_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "loki-primary"
    type = "loki"
    url  = "https://loki.internal.financialpartners.com"
  }
}

ℹ️ Loki's own json_data keys (e.g. maxLines, derivedFields) are not typed by this module β€” route them through json_data.extra if needed; see example 4 for the extra-map shape.

4 Β· CloudWatch-style data source (JSON config + secret keys)
module "cloudwatch_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "cloudwatch-primary"
    type = "cloudwatch"
  }

  json_data = {
    extra = {
      defaultRegion = "us-east-1"
      authType      = "keys"
    }
  }

  secure_json_data = {
    accessKey = var.cloudwatch_access_key
    secretKey = var.cloudwatch_secret_key
  }
}

⚠️ CloudWatch does not use tlsSkipVerify at all β€” this module still renders it in the JSON payload (defaulting false) for consistency across every data source type; Grafana ignores the unused key. See Β§15 Architecture Notes.

5 Β· InfluxDB with basic auth
module "influxdb_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name                = "myapp-metrics"
    type                = "influxdb"
    url                 = "http://influxdb.example.net:8086/"
    basic_auth_enabled  = true
    basic_auth_username = "username"
    database_name       = "dbname"
  }

  json_data = {
    extra = {
      authType = "default"
    }
  }

  secure_json_data = {
    basicAuthPassword = var.influxdb_basic_auth_password
  }
}

πŸ’‘ database_name is one of the fields the live schema documents as "(Required by some data source types)" β€” InfluxDB is one of them.

6 Β· Secure-TLS-verified default, explicit against a self-signed-looking internal host
module "internal_elasticsearch_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "elasticsearch-internal"
    type = "elasticsearch"
    url  = "https://elasticsearch.internal.financialpartners.com"
  }

  json_data = {
    tlsSkipVerify = false # explicit for clarity; this is already this module's default
  }
}

πŸ”’ TLS certificate verification stays on. If the target endpoint's certificate does not validate, fix the certificate chain β€” do not reach for tlsSkipVerify = true as the first response; see example 7 for the deliberate, explicit opt-out this module requires instead.

7 Β· ⚠️ Explicit opt-out β€” TLS skip-verify (caller must deliberately enable)
module "legacy_selfsigned_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "legacy-metrics-selfsigned"
    type = "prometheus"
    url  = "https://legacy-metrics.internal.financialpartners.com"
  }

  json_data = {
    tlsSkipVerify = true # nosec: legacy internal endpoint, cert rotation tracked in JIRA-1234, reviewed by Security
  }
}

⚠️ This is the only way to disable TLS verification in this module β€” the dedicated json_data.tlsSkipVerify field, never a value smuggled into json_data.extra (blocked by variables.tf's validation block; see example 14). Per this suite's secure-by-default convention, this opt-out is expected to carry a # nosec-style comment explaining the trade-off, reviewed accordingly.

8 Β· ⚠️ Explicit opt-out β€” basic auth enabled without TLS skip-verify
module "grafana_agent_metrics_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name                = "agent-metrics"
    type                = "prometheus"
    url                 = "https://agent-metrics.internal.financialpartners.com"
    basic_auth_enabled  = true
    basic_auth_username = "grafana-reader"
  }

  secure_json_data = {
    basicAuthPassword = var.agent_metrics_basic_auth_password
  }
}

πŸ’‘ Enabling basic_auth_enabled does not implicitly relax TLS posture β€” tlsSkipVerify still defaults false here. The two secure defaults in this module are independent opt-outs, not a single combined toggle.

9 Β· Data source set as the org-wide default
module "default_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name       = "prometheus-primary"
    type       = "prometheus"
    url        = "https://prometheus.internal.financialpartners.com"
    is_default = true
  }
}

⚠️ This module does not enforce single-default uniqueness across separate module instances β€” the provider's own docs note is_default "should only be true [for] a single data source"; the caller is responsible for ensuring only one instance across an org sets this.

10 Β· Data source with an explicit org scope
module "tenant_b_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name   = "tenant-b-metrics"
    type   = "prometheus"
    url    = "https://tenant-b-metrics.internal.financialpartners.com"
    org_id = tostring(module.tenant_b_org.org_id)
  }
}

ℹ️ org_id is exposed explicitly here rather than left to whatever org the aliased grafana provider instance defaults to β€” this suite's narrowest-explicit-scope convention. Wired from tf-mod-grafana-organization's org_id output via tostring(...) (that output is a Number; this resource's org_id argument is a String).

11 Β· Data source with an explicit, caller-chosen uid
module "stable_uid_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "shared-tempo"
    type = "tempo"
    url  = "https://tempo.internal.financialpartners.com"
    uid  = "shared-tempo-primary"
  }
}

⚠️ Treat uid as immutable once applied β€” see Β§6 Schema notes that bite and Β§15 Architecture Notes.

12 Β· Data source with custom HTTP headers (sensitive)
module "bearer_token_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "custom-api-metrics"
    type = "prometheus"
    url  = "https://custom-api-metrics.internal.financialpartners.com"
  }

  http_headers = {
    "Authorization" = "Bearer ${var.custom_api_bearer_token}"
  }
}

πŸ”’ var.http_headers is sensitive = true because the live grafana_data_source schema itself marks http_headers as (Map of String, Sensitive) β€” Terraform redacts this from plan/apply console output.

13 Β· Data source with a Private Data Source Connect network (Grafana Cloud only)
module "on_prem_via_pdc_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name                                   = "on-prem-mysql"
    type                                   = "mysql"
    url                                    = "mysql.on-prem.internal:3306"
    database_name                          = "reporting"
    private_data_source_connect_network_id = var.pdc_network_id
  }
}

πŸ”’ Usable only against data sources hosted in Grafana Cloud (see Β§8 Grafana Prerequisites). pdc_network_id is caller-supplied β€” no sibling module in this library's current catalog owns grafana_cloud_private_data_source_connect_network (see SCOPE.md's "Out of scope" section).

14 Β· ⚠️ What NOT to do β€” smuggling tlsSkipVerify into extra
module "bad_datasource" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name = "bad-example"
    type = "prometheus"
    url  = "https://example.internal.financialpartners.com"
  }

  json_data = {
    extra = {
      tlsSkipVerify = "true" # fails validation β€” do not do this
    }
  }
}

⚠️ This module's variable "json_data" carries a validation {} block that fails terraform plan with an actionable error when extra contains a tlsSkipVerify key β€” the only supported way to disable TLS verification is the dedicated json_data.tlsSkipVerify field (example 7).

15 Β· πŸ—οΈ End-to-end composition β€” organization β†’ data source β†’ downstream content
provider "grafana" {
  alias = "stack_primary"
  url   = "https://casey-primary.grafana.net"
  auth  = var.grafana_stack_service_account_token
}

module "tenant_b_org" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-organization.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  # See that module's own README for its real variable names/schema.
  # organization = { name = "Tenant B" }
}

module "tenant_b_prometheus" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-data-source.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  data_source = {
    name   = "tenant-b-prometheus"
    type   = "prometheus"
    url    = "https://tenant-b-prometheus.internal.financialpartners.com"
    org_id = tostring(module.tenant_b_org.org_id)
  }
}

module "tenant_b_dashboard" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-dashboard.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  # Illustrative only -- see that module's own README for its real variable names/schema.
  # config_json references module.tenant_b_prometheus.uid as the panel datasource UID
}

module "tenant_b_alert_rules" {
  source    = "git::https://github.com/microsoftexpert/tf-mod-grafana-alerting-rule-group.git?ref=v1.0.0"
  providers = { grafana = grafana.stack_primary }

  # Illustrative only -- see that module's own README for its real variable names/schema.
  # rule queries reference module.tenant_b_prometheus.uid as the datasource UID
}

πŸ’‘ This composition shows the full intended chain: an organization is provisioned first, this module scopes a Prometheus data source into that org via tostring(org_id), and downstream content-layer and alerting-layer modules (illustrated, not asserted as verified β€” confirm each module's own README for its exact variable names) query this data source by its uid output. Neither tf-mod-grafana-dashboard nor tf-mod-grafana-alerting-rule-group is fully authored as of this session.


πŸ“₯ Inputs

Variable Type (summary) Default Required
data_source object β€” keystone fields (see below) n/a Yes (name, type required inside)
json_data object β€” tlsSkipVerify + open extra map {} (tlsSkipVerify = false) No
secure_json_data map(string), sensitive = true {} No
http_headers map(string), sensitive = true {} No
Full object schemas
variable "data_source" {
  type = object({
    name                                   = string
    type                                   = string
    url                                    = optional(string)
    access_mode                            = optional(string, "proxy")
    basic_auth_enabled                     = optional(bool, false)
    basic_auth_username                    = optional(string)
    database_name                          = optional(string)
    username                               = optional(string)
    is_default                             = optional(bool, false)
    org_id                                 = optional(string)
    private_data_source_connect_network_id = optional(string)
    uid                                    = optional(string)
  })
}

variable "json_data" {
  type = object({
    tlsSkipVerify = optional(bool, false) # secure default -- caller must explicitly set true to opt out
    extra         = optional(map(string), {})
  })
  default = {}
}

variable "secure_json_data" {
  type      = map(string) # e.g. basicAuthPassword, privateKey, accessKey, secretKey
  default   = {}
  sensitive = true
}

variable "http_headers" {
  type      = map(string)
  default   = {}
  sensitive = true
}

🧾 Outputs

Output Description Sensitive / Conditional
uid Primary output. Data source's stable UID No
id Provider's own resource id (String) No
name Pass-through of configured name No
type Pass-through of configured type keyword No
url Pass-through of configured url, where set No
is_default Whether this data source is the org-wide default No

secure_json_data_encoded and http_headers are deliberately not output anywhere by this module β€” the caller already holds whatever it passed in via var.secure_json_data / var.http_headers.


🧠 Architecture Notes

  • tlsSkipVerify is always rendered, even for types that ignore it. main.tf renders json_data_encoded = jsonencode(merge({ tlsSkipVerify = var.json_data.tlsSkipVerify }, var.json_data.extra)) unconditionally β€” every data source this module manages carries an explicit tlsSkipVerify key in its JSON payload, defaulting false. Some data source types (e.g. cloudwatch) do not recognize this key at all; Grafana stores and ignores unused JSON keys harmlessly. This keeps the secure default uniformly enforced across every type rather than special-casing it per type.
  • The extra map cannot smuggle a tlsSkipVerify override. variables.tf's validation {} block on var.json_data fails terraform plan if extra contains a tlsSkipVerify key β€” the dedicated typed field is the only supported path to disabling TLS verification, keeping that decision visible in a plan diff rather than buried inside an arbitrary map.
  • Secrets are isolated into their own sensitive = true variables, not nested inside the main data_source object. Terraform's sensitive argument applies at the variable level; nesting a secret field inside a larger non-sensitive object would either force the entire object sensitive (masking non-secret fields in plan output unnecessarily) or leave the secret field unmasked. Splitting secure_json_data and http_headers into dedicated variables avoids both problems.
  • json_data_encoded / secure_json_data_encoded are rendered via jsonencode(...), not passed through as raw strings β€” the live schema documents both as plain serialized-JSON String attributes, confirmed against the live v4.40.1 schema (not merely inferred from example usage).
  • uid is immutable-after-set (inferred from the Import syntax and this library's grafana_folder.uid precedent, not independently confirmed as force-new against the live schema this session) β€” any sibling module or query configuration wiring this module's uid output depends on that value staying stable across applies.
  • Cloud vs. OSS applicability: grafana_data_source itself applies to both Cloud-hosted and self-managed OSS/Enterprise Grafana instances; private_data_source_connect_network_id is the one Cloud-only seam this module exposes (see Β§8 Grafana Prerequisites).
  • No for_each children. This is a standalone module β€” one grafana_data_source.this per module instance, no owned child resource type. Data source permissions are explicitly out of scope (see SCOPE.md).

🧱 Design Principles

Concern Safe default (this module) Opt-out
Basic auth data_source.basic_auth_enabled defaults false Caller sets data_source.basic_auth_enabled = true and supplies basic_auth_username + a secure_json_data.basicAuthPassword entry
TLS verification json_data.tlsSkipVerify defaults false (verification on); a caller cannot bypass this by setting the same-named key inside json_data.extra β€” blocked by validation Caller sets json_data.tlsSkipVerify = true explicitly, expected to carry a # nosec-style comment per this suite's secure-by-default convention
Data source credentials No credentials rendered by default (secure_json_data defaults {}); every value the caller does supply is carried in a sensitive = true variable, never echoed in any output Caller supplies var.secure_json_data entries; values are always caller-provisioned out of band (Key Vault, secrets manager, CI secret store), never generated inside this module
Custom HTTP headers Empty map ({}) by default; the variable itself is sensitive = true since the live schema marks http_headers Sensitive Caller supplies var.http_headers entries
Org/stack scope org_id defaults to null (provider's own default org) but is always an explicit, visible field on var.data_source rather than an implicit dependency on the aliased provider's own defaults Caller sets var.data_source.org_id

πŸš€ Runbook

cd C:\GitHubCode\newgrafanamodules\tf-mod-grafana-data-source
terraform init -backend=false
terraform validate
terraform fmt -check

Pin consumers at ?ref=v1.0.0, never a branch. This module is plan-only in this authoring pipeline β€” no apply is ever run here; a human applies from CI against a real Grafana target.


πŸ§ͺ Testing

terraform validate + terraform fmt -check together confirm: every field in var.data_source, var.json_data, var.secure_json_data, and var.http_headers type-checks against the schemas declared in variables.tf; the four validation {} blocks (name non-empty, type non-empty, access_mode enum, extra cannot contain tlsSkipVerify) are themselves syntactically valid; and the whole file set is canonically formatted. These checks run entirely offline β€” no GRAFANA_URL or Service Account token is required.

What this harness cannot catch: whether a given data_source.type's actual required-field shape (e.g. which of url/database_name/username/specific json_data/secure_json_data keys that type truly needs) is satisfied β€” the live schema itself does not encode this conditional requirement, so it is a platform-side check surfaced only at plan/apply against a real Grafana instance; whether uid collides with an existing data source; or whether private_data_source_connect_network_id actually resolves against a real Grafana Cloud PDC network.


πŸ’¬ Example Output

$ terraform output

id = "3"
is_default = false
name = "prometheus-primary"
type = "prometheus"
uid = "cJ4Q9m7Vk"
url = "https://prometheus.internal.financialpartners.com"

πŸ” Troubleshooting

Symptom Cause Fix
plan fails: "data_source.name must be a non-empty string" data_source.name was omitted or blank Set a non-empty, unique name
plan fails: "data_source.type must be a non-empty string" data_source.type was omitted or blank Set a valid data source type keyword (e.g. "prometheus", "loki", "cloudwatch")
plan fails: "data_source.access_mode must be exactly..." An invalid/mistyped access_mode value Use exactly "proxy" or "direct"
plan fails: "json_data.extra must not contain a "tlsSkipVerify" key" A caller tried to override TLS verification via the open extra map instead of the dedicated field Set json_data.tlsSkipVerify directly (see example 7)
apply fails with an authentication/connection error against the target data source Missing or incorrect secure_json_data credentials, or a genuinely invalid TLS certificate the module correctly refused to skip-verify Confirm the credential keys this data source type actually expects (see the provider's own per-type docs), or fix the certificate rather than setting tlsSkipVerify = true
apply fails referencing private_data_source_connect_network_id The target instance is not Grafana Cloud, or the PDC network ID does not exist Omit the field for OSS/Enterprise instances; confirm the ID against a real Grafana Cloud PDC network otherwise
Two data sources both show as the org default in the Grafana UI is_default = true was set on more than one module instance β€” this module does not enforce single-default uniqueness Set is_default = true on exactly one instance across the org
Changing data_source.uid triggers an unexpected destroy/recreate uid is treated as immutable-after-set (see Β§6 Schema notes that bite) Treat uid as write-once; provision a new data source instead of renaming an existing uid
terraform init can't find provider version ~> 4.0 Local provider cache is stale or offline Re-run terraform init -backend=false -upgrade, or confirm network access to the Terraform Registry

πŸ”— Related Docs

  • Provider resource docs: grafana_data_source (grafana/grafana provider, Terraform Registry)
  • This module's SCOPE.md β€” lightweight cross-module contract, required auth/scope, Grafana prerequisites, provider gotchas, design decisions
  • Sibling modules referenced above: tf-mod-grafana-organization, tf-mod-grafana-dashboard, tf-mod-grafana-alerting-rule-group (dashboard and alerting-rule-group not yet fully authored as of this session β€” treat their example call shapes above as illustrative)

About

No description or website provided.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages