Skip to content

Commit 0edb916

Browse files
committed
fix(config): accept the wider additional_endpoints input forms
Migrating the metrics encoder and forwarder to the typed model dropped the component serde on additional_endpoints, whose PickFirst<DisplayFromStr> and OneOrMany used to accept two shapes the generated HashMap<String, Vec<String>> deserializer rejects: - the whole map as a JSON string, which is how an env var arrives (DD_ADDITIONAL_ENDPOINTS='{"https://app.datadoghq.com":["key"]}'), and - a bare string in place of a one-element key list for a host. Either shape now fails deserialization outright, so a dual-shipping deployment configured through the environment fails to start. Restore both in normalize_datadog_input_forms, alongside the existing dogstatsd coercions: parse the JSON-string form into an object, then wrap any bare-string host value into a one-element array.
1 parent dbdba58 commit 0edb916

1 file changed

Lines changed: 51 additions & 0 deletions

File tree

  • lib/agent-data-plane-config-system/src

lib/agent-data-plane-config-system/src/system.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,28 @@ fn normalize_datadog_input_forms(merged: &mut serde_json::Value) {
256256
*value = parsed;
257257
}
258258
}
259+
260+
// `additional_endpoints` is a `HashMap<String, Vec<String>>` in the schema, but the deleted
261+
// component serde accepted two wider shapes the generated deserializer rejects: the whole value
262+
// as a JSON string (the form an env var produces, for example
263+
// `DD_ADDITIONAL_ENDPOINTS='{"https://app.datadoghq.com":["key"]}'`), and a bare string in place
264+
// of a one-element key list for a host. Restore both so dual-shipping config keeps deserializing.
265+
// First parse the JSON-string form into the object the deserializer expects; if it is not valid
266+
// JSON, leave the string in place so the downstream deserializer surfaces the error.
267+
if let Some(value @ serde_json::Value::String(_)) = object.get_mut("additional_endpoints") {
268+
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(value.as_str().expect("value matched String")) {
269+
*value = parsed;
270+
}
271+
}
272+
// Then wrap any bare-string host value into a one-element array (matching the old `OneOrMany`).
273+
if let Some(serde_json::Value::Object(map)) = object.get_mut("additional_endpoints") {
274+
for keys in map.values_mut() {
275+
if matches!(keys, serde_json::Value::String(_)) {
276+
let single = std::mem::take(keys);
277+
*keys = serde_json::Value::Array(vec![single]);
278+
}
279+
}
280+
}
259281
}
260282

261283
/// Translates the Datadog and Saluki-only sources into one [`SalukiConfiguration`], returning every
@@ -558,6 +580,35 @@ mod tests {
558580
}
559581
}
560582

583+
/// `additional_endpoints` accepts a JSON string (the Agent's env-var form) and a bare string in
584+
/// place of a one-element key list, as well as the native map-of-lists, all landing on
585+
/// `shared.endpoints.additional_endpoints`.
586+
#[tokio::test]
587+
async fn additional_endpoints_accepts_json_string_scalar_or_map() {
588+
let expected: std::collections::HashMap<String, Vec<String>> =
589+
[("https://app.datadoghq.com".to_string(), vec!["key".to_string()])]
590+
.into_iter()
591+
.collect();
592+
593+
for value in [
594+
// Env-var form: the whole map arrives as a JSON string.
595+
json!("{\"https://app.datadoghq.com\":[\"key\"]}"),
596+
// Env-var form with a bare string value in place of a one-element key list.
597+
json!("{\"https://app.datadoghq.com\":\"key\"}"),
598+
// Native map with a bare string in place of a one-element key list.
599+
json!({ "https://app.datadoghq.com": "key" }),
600+
// Native map of key lists.
601+
json!({ "https://app.datadoghq.com": ["key"] }),
602+
] {
603+
let (raw_map, _) =
604+
ConfigurationLoader::for_tests(Some(json!({ "additional_endpoints": value })), None, false).await;
605+
606+
let system =
607+
ConfigurationSystem::load(raw_map, EnvOverlayMode::Fallback).expect("additional_endpoints translates");
608+
assert_eq!(system.config().shared.endpoints.additional_endpoints, expected);
609+
}
610+
}
611+
561612
#[tokio::test]
562613
async fn mapper_profile_other_required_fields_remain_required() {
563614
let cases = [

0 commit comments

Comments
 (0)