Skip to content

[datadog_dashboard] cloud_cost data_source read back as "metrics" causing perpetual drift #3639

@nickpell

Description

@nickpell

Datadog Terraform Provider Version

v4.3.0

Terraform Version

v1.14.7

What resources or data sources are affected?

  • datadog_dashboard

Terraform Configuration Files

resource "datadog_dashboard" "datadog_cost" {
  title       = "Datadog Cost Overview"
  layout_type = "ordered"

  widget {
    group_definition {
      title       = "Summary"
      layout_type = "ordered"

      widget {
        query_value_definition {
          title     = "Estimated MTD Datadog Spend"
          live_span = "1mo"

          request {
            formula {
              formula_expression = "query1"
            }

            query {
              metric_query {
                name        = "query1"
                data_source = "cloud_cost"
                query       = "sum:datadog.cost.estimated{cost_type:usage}"
                aggregator  = "sum"
              }
            }
          }
        }
      }
    }
  }
}

Relevant debug or panic output

JSON plan output showing the drift on every metric_query block:

data_source: "metrics" → "cloud_cost"

Full list of diffs from terraform show -json on the plan:

widget[0].group.widget[0].query_value.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[0].group.widget[1].query_value.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[1].group.widget[0].timeseries.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[1].group.widget[1].timeseries.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[2].group.widget[0].toplist.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[2].group.widget[1].toplist.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"
widget[3].group.widget[0].change.request[0].query[0].metric_query[0].data_source: "metrics" -> "cloud_cost"

Expected Behavior

Setting data_source = "cloud_cost" on a metric_query block inside a dashboard widget should be persisted in state after apply. Subsequent plans should show no changes.

Actual Behavior

After a successful apply, the next terraform plan (or refresh) reads the data_source attribute back from the Datadog API as "metrics" instead of "cloud_cost". This causes every plan to show an update for every widget that uses cloud cost queries, even though the dashboard is configured correctly in the Datadog UI.

The drift is purely in the provider's read/flatten logic — the dashboard itself works fine and shows cloud cost data as expected.

Steps to Reproduce

  1. Create a datadog_dashboard with any widget using metric_query { data_source = "cloud_cost" ... }
  2. terraform apply — applies successfully
  3. terraform plan — shows update needed on every widget with data_source: "metrics" -> "cloud_cost"
  4. Repeat step 2-3 indefinitely — the diff never resolves

Important Factoids

Root cause

The bug is in buildDatadogMetricQuery in resource_datadog_dashboard.go. The function hardcodes data_source to "metrics" and never reads the user-supplied value from the HCL input:

func buildDatadogMetricQuery(data map[string]interface{}) *datadogV1.FormulaAndFunctionQueryDefinition {
	dataSource := datadogV1.FormulaAndFunctionMetricDataSource("metrics")   // <-- hardcoded, ignores data["data_source"]
	metricQuery := datadogV1.NewFormulaAndFunctionMetricQueryDefinition(dataSource, data["name"].(string), data["query"].(string))
	// ...
}

The read path (buildTerraformQuery) correctly reads data_source from the API response via GetDataSourceOk(), so state accurately reflects what the API returns. But since the write path always sends "metrics", the API stores or returns "metrics", and Terraform detects drift against the HCL-specified "cloud_cost".

Suggested fix

Read data_source from the HCL input instead of hardcoding it, matching the pattern used by other query builders (e.g., buildDatadogFormulaAndFunctionAPMResourceStatsQuery which uses data["data_source"].(string)):

func buildDatadogMetricQuery(data map[string]interface{}) *datadogV1.FormulaAndFunctionQueryDefinition {
	dataSource := datadogV1.FormulaAndFunctionMetricDataSource("metrics")
	if v, ok := data["data_source"].(string); ok && len(v) != 0 {
		dataSource = datadogV1.FormulaAndFunctionMetricDataSource(v)
	}
	metricQuery := datadogV1.NewFormulaAndFunctionMetricQueryDefinition(dataSource, data["name"].(string), data["query"].(string))
	// ...
}

This preserves backwards compatibility (defaulting to "metrics" when data_source is unset, which aligns with the schema Default: "metrics"), while correctly honoring data_source = "cloud_cost" when explicitly set.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions