Manages a single
grafana_data_sourceβ connection, auth mode, and JSON-encoded configuration β as one standalone keystone resource. Targetsgrafana/grafana(~> 4.0).
- π Creates and manages a
grafana_data_sourceβname/typerequired; connection URL, access mode, basic auth, database/username, org scope, Private Data Source Connect network ID, and customuidall optional per the live v4.40.1 schema. - 𧬠Renders
json_data_encodedandsecure_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 = falseandjson_data.tlsSkipVerify = false(TLS certificate verification stays on). Avalidation {}block blocks a caller from smuggling atlsSkipVerifyoverride into the openextrajson_data map. - π΅οΈ Isolates every credential-shaped input β
secure_json_data,http_headersβ into its ownsensitive = truevariable, and never re-exposes either in any output. - π Emits
uidas 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_enabledandtlsSkipVerifyexplicit, secure-by-default typed fields β rather than leaving them buried inside a caller-supplied raw JSON blob β means aterraform plandiff 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.
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 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;
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.
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;
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 |
| 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_encodedandsecure_json_data_encodedare 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 onlytlsSkipVerifyis typed explicitly (forced to a secure default); everything else flows through an openextramap (see Β§15 Architecture Notes).http_headersis schema-marked(Map of String, Sensitive)by the provider itself β this module mirrors that by marking its ownvar.http_headerssensitive = true.uidis 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'sgrafana_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 thegrafana_folder.uidprecedent, not a schema-confirmed fact.grafana_data_source.idis a String ("The ID of this resource") β informational only; the provider's own Import syntax usesuid, notid, reinforcinguidas 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_modedefaults to"proxy"per the live schema's own field description; this module mirrors that default explicitly invariables.tfrather than leaving it to the provider alone, consistent with this suite's "make the type the contract" governing idea.basic_auth_username,database_name, andusernameare each documented as"(Required by some data source types)"β the live schema does not encode a mechanism to make a field's requiredness conditional ontype, and neither does this module; a caller supplying a mismatched shape for a giventypewill discover it only atapplytime against a live Grafana instance, not atplantime.
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.
private_data_source_connect_network_idis 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_sourcevaries entirely bydata_source.type(e.g.url+basic_auth_usernameforinfluxdb;json_data.extra.defaultRegion+secure_json_data.accessKey/secretKeyforcloudwatch). 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_sourceresource itself.
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)
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).
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 |
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 customuid(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 = trueis an explicit caller opt-in (the module default isfalse).basicAuthPasswordflows throughvar.secure_json_data,sensitive = true, never through a plain-text variable. TLS verification is untouched here βtlsSkipVerifystill defaultsfalse.
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_datakeys (e.g.maxLines,derivedFields) are not typed by this module β route them throughjson_data.extraif needed; see example 4 for theextra-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 usetlsSkipVerifyat all β this module still renders it in the JSON payload (defaultingfalse) 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_nameis 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 = trueas 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 dedicatedjson_data.tlsSkipVerifyfield, never a value smuggled intojson_data.extra(blocked byvariables.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_enableddoes not implicitly relax TLS posture βtlsSkipVerifystill defaultsfalsehere. 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 noteis_default"should only betrue[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_idis exposed explicitly here rather than left to whatever org the aliasedgrafanaprovider instance defaults to β this suite's narrowest-explicit-scope convention. Wired fromtf-mod-grafana-organization'sorg_idoutput viatostring(...)(that output is a Number; this resource'sorg_idargument 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"
}
}
β οΈ Treatuidas 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_headersissensitive = truebecause the livegrafana_data_sourceschema itself markshttp_headersas(Map of String, Sensitive)β Terraform redacts this fromplan/applyconsole 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_idis caller-supplied β no sibling module in this library's current catalog ownsgrafana_cloud_private_data_source_connect_network(seeSCOPE.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'svariable "json_data"carries avalidation {}block that failsterraform planwith an actionable error whenextracontains atlsSkipVerifykey β the only supported way to disable TLS verification is the dedicatedjson_data.tlsSkipVerifyfield (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 itsuidoutput. Neithertf-mod-grafana-dashboardnortf-mod-grafana-alerting-rule-groupis fully authored as of this session.
| 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
}| 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.
tlsSkipVerifyis always rendered, even for types that ignore it.main.tfrendersjson_data_encoded = jsonencode(merge({ tlsSkipVerify = var.json_data.tlsSkipVerify }, var.json_data.extra))unconditionally β every data source this module manages carries an explicittlsSkipVerifykey in its JSON payload, defaultingfalse. 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
extramap cannot smuggle atlsSkipVerifyoverride.variables.tf'svalidation {}block onvar.json_datafailsterraform planifextracontains atlsSkipVerifykey β the dedicated typed field is the only supported path to disabling TLS verification, keeping that decision visible in aplandiff rather than buried inside an arbitrary map. - Secrets are isolated into their own
sensitive = truevariables, not nested inside the maindata_sourceobject. Terraform'ssensitiveargument 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 inplanoutput unnecessarily) or leave the secret field unmasked. Splittingsecure_json_dataandhttp_headersinto dedicated variables avoids both problems. json_data_encoded/secure_json_data_encodedare rendered viajsonencode(...), 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).uidis immutable-after-set (inferred from the Import syntax and this library'sgrafana_folder.uidprecedent, not independently confirmed as force-new against the live schema this session) β any sibling module or query configuration wiring this module'suidoutput depends on that value staying stable across applies.- Cloud vs. OSS applicability:
grafana_data_sourceitself applies to both Cloud-hosted and self-managed OSS/Enterprise Grafana instances;private_data_source_connect_network_idis the one Cloud-only seam this module exposes (see Β§8 Grafana Prerequisites). - No
for_eachchildren. This is a standalone module β onegrafana_data_source.thisper module instance, no owned child resource type. Data source permissions are explicitly out of scope (seeSCOPE.md).
| 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 |
cd C:\GitHubCode\newgrafanamodules\tf-mod-grafana-data-source
terraform init -backend=false
terraform validate
terraform fmt -checkPin 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.
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.
$ terraform output
id = "3"
is_default = false
name = "prometheus-primary"
type = "prometheus"
uid = "cJ4Q9m7Vk"
url = "https://prometheus.internal.financialpartners.com"
| 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 |
- Provider resource docs:
grafana_data_source(grafana/grafanaprovider, 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)