diff --git a/grafana/datasource/datasource.tf b/grafana/datasource/datasource.tf index a2eec52..2fc1eab 100644 --- a/grafana/datasource/datasource.tf +++ b/grafana/datasource/datasource.tf @@ -1,52 +1,81 @@ -# main.tf - -# Step 1: Create all Grafana data sources with their basic configuration. -# This resource establishes the fundamental properties of each data source. +# Step 1: Create the basic "shell" of each datasource. resource "grafana_data_source" "this" { for_each = var.datasources - name = each.key - type = each.value.type - url = var.datasource_urls[each.value.url_key] + name = each.key + type = each.value.type + url = var.datasource_urls[each.value.url_key] is_default = coalesce(each.value.is_default, false) - basic_auth_enabled = true - basic_auth_username = var.datasource_users[each.value.user_key] + # For HTTP Basic Auth (Loki, Prometheus, etc.) + # FIX: Changed 'user_key' to 'basic_auth_user_key' to match your variables.tf + basic_auth_enabled = each.value.basic_auth_user_key != null + basic_auth_username = each.value.basic_auth_user_key != null ? var.datasource_users[each.value.basic_auth_user_key] : null - secure_json_data_encoded = jsonencode({ - basicAuthPassword = var.datasource_passwords[each.value.pass_key] - }) + # For database usernames (like Postgres) + # This sets the username initially. + username = each.value.db_user_key != null ? var.datasource_users[each.value.db_user_key] : null - # Encodes initial, non-dependent JSON data. - # Configurations that depend on other datasource UIDs will be handled separately. - json_data_encoded = each.value.json_data != null ? jsonencode(each.value.json_data) : null - - # ignore changes made by the _config resource + # CRITICAL FIX: This resource must ignore attributes that are + # managed by the other 'config' resources below. lifecycle { ignore_changes = [ json_data_encoded, + secure_json_data_encoded, + # Also ignore username, as it can be managed/reported back differently by the API. + username, ] } } -# Step 2: Apply Loki-specific 'derivedFields' configuration. -# This resource targets Loki data sources that need to link to another data source (like Tempo). -# It runs after the initial data sources are created to resolve the UIDs. -resource "grafana_data_source_config" "loki_derived_fields" { - # Filter for datasources that are of type 'loki' and have 'derived_fields' defined. +# Step 2: Apply the main json_data for datasources like PostgreSQL. +resource "grafana_data_source_config" "json_data_main" { for_each = { for k, v in var.datasources : k => v - if v.type == "loki" && v.derived_fields != null + if v.json_data != null && v.derived_fields == null && v.traces_to_logs == null } - # The UID of the Loki data source to configure. uid = grafana_data_source.this[each.key].uid - # Construct the json_data with the derivedFields. + json_data_encoded = jsonencode(each.value.json_data) + + # This config must ignore the password, which is managed by the 'passwords' resource. + lifecycle { + ignore_changes = [secure_json_data_encoded] + } +} + +# Step 3: Apply passwords to all datasources that require one. +resource "grafana_data_source_config" "passwords" { + for_each = { + for k, v in var.datasources : k => v if v.pass_key != null + } + + uid = grafana_data_source.this[each.key].uid + + secure_json_data_encoded = jsonencode( + each.value.type == "grafana-postgresql-datasource" ? { + password = var.datasource_passwords[each.value.pass_key] + } : { + basicAuthPassword = var.datasource_passwords[each.value.pass_key] + } + ) + + # This config must ignore the main json_data, which is managed elsewhere. + lifecycle { + ignore_changes = [json_data_encoded] + } +} + +# Step 4: Apply Loki-specific 'derivedFields' configuration. +resource "grafana_data_source_config" "loki_derived_fields" { + for_each = { + for k, v in var.datasources : k => v if v.type == "loki" && v.derived_fields != null + } + uid = grafana_data_source.this[each.key].uid json_data_encoded = jsonencode({ derivedFields = [ for field in each.value.derived_fields : { - # The UID of the target data source (e.g., Tempo). datasourceUid = grafana_data_source.this[field.target_datasource_name].uid matcherRegex = field.matcher_regex name = field.name @@ -54,24 +83,21 @@ resource "grafana_data_source_config" "loki_derived_fields" { } ] }) + + # This config must ignore the password, which is managed elsewhere. + lifecycle { + ignore_changes = [secure_json_data_encoded] + } } -# Step 3: Apply Tempo-specific 'tracesToLogsV2' configuration. -# This resource targets Tempo data sources that need to link back to a logging data source (like Loki). +# Step 5: Apply Tempo-specific 'tracesToLogsV2' configuration. resource "grafana_data_source_config" "tempo_traces_to_logs" { - # Filter for datasources that are of type 'tempo' and have 'traces_to_logs' defined. for_each = { - for k, v in var.datasources : k => v - if v.type == "tempo" && v.traces_to_logs != null + for k, v in var.datasources : k => v if v.type == "tempo" && v.traces_to_logs != null } - - # The UID of the Tempo data source to configure. - uid = grafana_data_source.this[each.key].uid - - # Construct the json_data with the tracesToLogsV2 settings. + uid = grafana_data_source.this[each.key].uid json_data_encoded = jsonencode({ tracesToLogsV2 = { - # The UID of the target data source (e.g., Loki). datasourceUid = grafana_data_source.this[each.value.traces_to_logs.target_datasource_name].uid query = each.value.traces_to_logs.query customQuery = coalesce(each.value.traces_to_logs.custom_query, true) @@ -81,4 +107,9 @@ resource "grafana_data_source_config" "tempo_traces_to_logs" { spanEndTimeShift = each.value.traces_to_logs.span_end_time_shift } }) -} + + # This config must ignore the password, which is managed elsewhere. + lifecycle { + ignore_changes = [secure_json_data_encoded] + } +} \ No newline at end of file diff --git a/grafana/datasource/variables.tf b/grafana/datasource/variables.tf index cbf19db..94ebfa8 100644 --- a/grafana/datasource/variables.tf +++ b/grafana/datasource/variables.tf @@ -1,32 +1,33 @@ -# variables.tf - -# Define datasources (non-sensitive metadata only) variable "datasources" { description = <