Skip to content

Commit 02b5843

Browse files
jszwedkoclaude
andcommitted
fix(config): honor site when dd_url equals the default-derived URL
Backport of #2028 to releases/1.1.0. The Core Agent's config stream sends dd_url at its schema default (https://app.datadoghq.com) for every configuration, even when the operator only set site. ADP was treating that default as an explicit override and routing all traffic to the US1 intake, so site was effectively ignored (#1965). Filter the default value at deserialization: a dd_url equal to the default-derived URL is treated as None, allowing site to determine the endpoint. This only affects the serde path; programmatic callers such as set_dd_url bypass serde and are unaffected. Fixes #1965. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 1d08035 commit 02b5843

1 file changed

Lines changed: 81 additions & 1 deletion

File tree

lib/saluki-components/src/common/datadog/endpoints.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,31 @@ static DD_URL_REGEX: LazyLock<Regex> =
2121

2222
pub const DEFAULT_SITE: &str = "datadoghq.com";
2323

24+
/// The primary endpoint URL that is constructed when both `site` and `dd_url` are at their defaults.
25+
///
26+
/// The Core Agent sends `dd_url` at this value even when the operator only configured `site`.
27+
/// A `dd_url` equal to this constant carries no override intent and must not shadow `site`.
28+
const DEFAULT_PRIMARY_ENDPOINT: &str = "https://app.datadoghq.com";
29+
2430
fn default_site() -> String {
2531
DEFAULT_SITE.to_owned()
2632
}
2733

34+
/// Deserializes an optional `dd_url`, treating the schema-default URL as absent.
35+
///
36+
/// The Core Agent always sends `dd_url` at its schema default (`https://app.datadoghq.com`) even
37+
/// when the operator only configured `site`. Filtering here, at deserialization, ensures that a
38+
/// value equal to the default is treated as `None`, allowing `site` to determine the endpoint. This
39+
/// only affects the serde path; programmatic callers such as `set_dd_url` bypass serde and are
40+
/// unaffected.
41+
fn deserialize_dd_url<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
42+
where
43+
D: serde::Deserializer<'de>,
44+
{
45+
let val = Option::<String>::deserialize(deserializer)?;
46+
Ok(val.filter(|url| url.as_str() != DEFAULT_PRIMARY_ENDPOINT))
47+
}
48+
2849
/// Error type for invalid endpoints.
2950
#[derive(Debug, Snafu)]
3051
#[snafu(context(suffix(false)))]
@@ -136,7 +157,7 @@ pub struct EndpointConfiguration {
136157
/// which are both useful when proxying traffic to an intermediate destination before forwarding to Datadog.
137158
///
138159
/// Defaults to unset.
139-
#[serde(default, alias = "url")]
160+
#[serde(default, alias = "url", deserialize_with = "deserialize_dd_url")]
140161
dd_url: Option<String>,
141162

142163
/// Enables sending data to multiple endpoints and/or with multiple API keys via dual shipping.
@@ -638,4 +659,63 @@ mod tests {
638659
.expect("error calculating override API endpoint");
639660
assert_eq!(expected_endpoint, resolved.endpoint().to_string());
640661
}
662+
663+
#[test]
664+
fn deserialize_dd_url_filters_default_value() {
665+
// The default dd_url (what the Agent sends when operator only configured site) should
666+
// deserialize to None so that site takes precedence.
667+
let config_str = r#"{"api_key": "test-key", "dd_url": "https://app.datadoghq.com"}"#;
668+
let config: EndpointConfiguration = serde_json::from_str(config_str).expect("deserialization should succeed");
669+
assert_eq!(None, config.dd_url);
670+
}
671+
672+
#[test]
673+
fn deserialize_dd_url_preserves_explicit_override() {
674+
// A dd_url that differs from the default should deserialize as-is.
675+
let config_str = r#"{"api_key": "test-key", "dd_url": "https://proxy.internal.example.com:3128"}"#;
676+
let config: EndpointConfiguration = serde_json::from_str(config_str).expect("deserialization should succeed");
677+
assert_eq!(
678+
Some("https://proxy.internal.example.com:3128".to_string()),
679+
config.dd_url
680+
);
681+
}
682+
683+
#[test]
684+
fn set_dd_url_is_not_filtered() {
685+
// Programmatic calls to set_dd_url bypass serde and are never filtered, even if set to the default value.
686+
// This is important for MRF and other override paths that may explicitly set the default URL.
687+
let config_str = r#"{"api_key": "test-key", "site": "datadoghq.eu"}"#;
688+
let mut config: EndpointConfiguration =
689+
serde_json::from_str(config_str).expect("deserialization should succeed");
690+
config.set_dd_url("https://app.datadoghq.com".to_string());
691+
assert_eq!(Some("https://app.datadoghq.com".to_string()), config.dd_url);
692+
}
693+
694+
#[test]
695+
fn site_takes_precedence_when_dd_url_is_default() {
696+
// When dd_url is at its default (sent by Agent with source="default"), site should determine the endpoint.
697+
// This is tested through deserialization so the default-filtering occurs.
698+
let config_str = r#"{"api_key": "test-key", "site": "datadoghq.eu", "dd_url": "https://app.datadoghq.com"}"#;
699+
let config: EndpointConfiguration = serde_json::from_str(config_str).expect("deserialization should succeed");
700+
701+
let resolved = config
702+
.build_primary_endpoint(None)
703+
.expect("error building primary endpoint");
704+
// The site path applies a version prefix, so assert the host resolves to the eu site rather than the
705+
// default US1 intake.
706+
let host = resolved.endpoint().host_str().unwrap();
707+
assert!(host.ends_with("datadoghq.eu"), "expected eu site, got {host}");
708+
}
709+
710+
#[test]
711+
fn explicit_dd_url_overrides_site() {
712+
// A dd_url that diverges from the default should take precedence over site.
713+
let config_str = r#"{"api_key": "test-key", "site": "datadoghq.eu", "dd_url": "https://dogpound.io/"}"#;
714+
let config: EndpointConfiguration = serde_json::from_str(config_str).expect("deserialization should succeed");
715+
716+
let resolved = config
717+
.build_primary_endpoint(None)
718+
.expect("error building primary endpoint");
719+
assert_eq!("dogpound.io", resolved.endpoint().host_str().unwrap());
720+
}
641721
}

0 commit comments

Comments
 (0)