# 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] is_default = coalesce(each.value.is_default, false) # 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 # 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 # 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 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.json_data != null && v.derived_fields == null && v.traces_to_logs == null } uid = grafana_data_source.this[each.key].uid 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 : { datasourceUid = grafana_data_source.this[field.target_datasource_name].uid matcherRegex = field.matcher_regex name = field.name url = field.url } ] }) # This config must ignore the password, which is managed elsewhere. lifecycle { ignore_changes = [secure_json_data_encoded] } } # Step 5: Apply Tempo-specific 'tracesToLogsV2' configuration. resource "grafana_data_source_config" "tempo_traces_to_logs" { for_each = { for k, v in var.datasources : k => v if v.type == "tempo" && v.traces_to_logs != null } uid = grafana_data_source.this[each.key].uid json_data_encoded = jsonencode({ tracesToLogsV2 = { 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) filterBySpanID = coalesce(each.value.traces_to_logs.filter_by_span_id, false) filterByTraceID = coalesce(each.value.traces_to_logs.filter_by_trace_id, false) spanStartTimeShift = each.value.traces_to_logs.span_start_time_shift 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] } }