From e0dd85fcee6078f16c79d0b856ad1eac3829c0b5 Mon Sep 17 00:00:00 2001 From: iunanua Date: Tue, 9 Jun 2026 16:44:42 +0200 Subject: [PATCH 1/4] refactor(rc): migrate remote config client to libdd-remote-config Replaces the in-tree RC client (hyper poller, hand-rolled TUF parsing, base64/SHA256 caching, ConfigState bookkeeping, custom ProductRegistry, path extraction) with a thin adapter over libdd-remote-config's SingleChangesFetcher. The ApmTracingConfig parser is preserved verbatim and registered through the RemoteConfigContent trait, so all parser semantics (null-vs-missing distinction, [0.0, 1.0] rate validation, service_target gating, rules/rate composition, default-provenance catch-all) are unchanged. libdd-remote-config is pinned to libdatadog rev 2e6214d80 via git + [patch.crates-io] for libdd-common and libdd-trace-protobuf, so existing libdd-* version pins on crates.io stay intact. Drop now-unused deps (base64, sha2, hyper-util, http-body-util) and add libdd-remote-config/https to the local https feature. Co-Authored-By: Claude Opus 4.7 --- Cargo.lock | 398 ++- Cargo.toml | 19 + LICENSE-3rdparty.csv | 24 +- datadog-opentelemetry/Cargo.toml | 11 +- .../src/core/configuration/remote_config.rs | 2477 +++-------------- instrumentation/Cargo.lock | 420 ++- instrumentation/Cargo.toml | 8 + 7 files changed, 1138 insertions(+), 2219 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62b7e173..d0a39c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -160,12 +169,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -289,7 +292,10 @@ version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ + "iana-time-zone", "num-traits", + "serde", + "windows-link", ] [[package]] @@ -442,7 +448,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.5", + "itertools", "num-traits", "once_cell", "oorandom", @@ -463,7 +469,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -516,6 +522,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "datadog-opentelemetry" version = "0.3.3" @@ -523,19 +564,17 @@ dependencies = [ "anyhow", "arc-swap", "assert_unordered", - "base64 0.21.7", "criterion", "datadog-opentelemetry", "foldhash 0.1.5", "hashbrown 0.15.5", - "http-body-util", "hyper", - "hyper-util", "libc", "libdd-capabilities-impl", "libdd-common", "libdd-data-pipeline", "libdd-library-config", + "libdd-remote-config", "libdd-sampling", "libdd-telemetry", "libdd-tinybytes", @@ -554,7 +593,6 @@ dependencies = [ "rustc_version_runtime", "serde", "serde_json", - "sha2", "test-case", "thiserror 1.0.69", "tokio", @@ -565,6 +603,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "diff" version = "0.1.13" @@ -592,6 +640,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -835,7 +889,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -853,6 +907,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.5" @@ -887,7 +947,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "headers-core", "http", @@ -977,7 +1037,7 @@ dependencies = [ "assert-json-diff", "async-object-pool", "async-trait", - "base64 0.22.1", + "base64", "bytes", "crossbeam-utils", "form_urlencoded", @@ -1059,7 +1119,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1079,6 +1139,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1167,6 +1251,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1188,6 +1278,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1242,15 +1343,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1305,8 +1397,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdd-capabilities" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ce42b411956272ea0ff851dddbd8ea4dae251192ffdbc50e90e20737ffd600" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", @@ -1317,8 +1408,7 @@ dependencies = [ [[package]] name = "libdd-capabilities-impl" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a6210d654b578a52ac4abe31f94b88f52740c8f2a62adc969cad240eae319d" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "bytes", "http", @@ -1331,8 +1421,7 @@ dependencies = [ [[package]] name = "libdd-common" version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af81e6953fab2814792b8893a2cee9f16aad1b71cb3f8720b7256f28d6a2d8d" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", @@ -1366,8 +1455,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda4a989dc3fc0ebd0523521f52d18ee72172d9f275762b65a205ec9b960eb9" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", @@ -1386,7 +1474,7 @@ dependencies = [ "libdd-telemetry", "libdd-tinybytes", "libdd-trace-obfuscation", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-stats", "libdd-trace-utils", "rmp-serde", @@ -1402,8 +1490,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b31b2435e2e8eaba0e35a96df3e1407b68f8ef76055383ceb2ba5a09e5a1bb5" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "prost", ] @@ -1411,8 +1498,7 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f79afd4a3f84f80f6b631e4446d09131c156f1179295e390e9b4dc2bfa527cf" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "cadence", @@ -1430,7 +1516,7 @@ checksum = "726e95f0f543da854a24999b16601afd2ca2c6b0c1bdc511bf52c5cd0635f7ad" dependencies = [ "anyhow", "libc", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "memfd", "prost", "rand 0.8.6", @@ -1440,6 +1526,31 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "libdd-remote-config" +version = "0.1.0" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" +dependencies = [ + "anyhow", + "base64", + "futures-util", + "http", + "http-body-util", + "libdd-common", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "manual_future", + "serde", + "serde_json", + "serde_with", + "sha2", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-util", + "tracing", + "uuid", +] + [[package]] name = "libdd-sampling" version = "4.0.0" @@ -1455,8 +1566,7 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9c295f7b55ec78e261537627a75be4e926a94e2b0014e98392b3f98e1957aa" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "async-trait", "futures", @@ -1473,12 +1583,11 @@ dependencies = [ [[package]] name = "libdd-telemetry" version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9626d441cee43418a080cb2fae694d86f62eb965282ff9d0a1c99058658eb3ad" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", + "base64", "bytes", "futures", "hashbrown 0.15.5", @@ -1501,8 +1610,7 @@ dependencies = [ [[package]] name = "libdd-tinybytes" version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97db80e3f3f9d19643ac444d6267951a5dab622ad9828c51149d49150625cfe8" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "serde", ] @@ -1510,23 +1618,21 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab12e095f3589d02ceed76556e7aa8a1b5bc928a900e143b624e2331294e54b5" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", ] [[package]] name = "libdd-trace-obfuscation" version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44197ef58464bd75d002d10e02f702096dc52d68d08b7355860b534a30f8ac5b" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "fluent-uri", "libdd-common", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-utils", "log", "percent-encoding", @@ -1545,11 +1651,20 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "libdd-trace-protobuf" +version = "3.0.2" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" +dependencies = [ + "prost", + "serde", + "serde_bytes", +] + [[package]] name = "libdd-trace-stats" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32bfbd154a6d6a5b452458850adfa1dd3e36d567ad6ae606b16b8bfeb97fa7a" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", @@ -1562,7 +1677,7 @@ dependencies = [ "libdd-ddsketch", "libdd-shared-runtime", "libdd-trace-obfuscation", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-utils", "rmp-serde", "serde", @@ -1574,11 +1689,10 @@ dependencies = [ [[package]] name = "libdd-trace-utils" version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58340581c2d7f02782e5e0ace77cd42bca18938761f359079a7ff534a24bca99" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "cargo-platform", "cargo_metadata", @@ -1589,13 +1703,13 @@ dependencies = [ "http-body-util", "httpmock", "hyper", - "indexmap", + "indexmap 2.14.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", "libdd-tinybytes", "libdd-trace-normalization", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "prost", "rand 0.8.6", "rmp", @@ -1650,6 +1764,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "manual_future" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c72f11f1d8e0c453cbd8042dfb83c2b50384f78a5a5d41019627c5f2062ece" +dependencies = [ + "futures-util", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1712,6 +1835,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -1970,6 +2099,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2060,7 +2195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools", "proc-macro2", "quote", "syn", @@ -2303,7 +2438,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", @@ -2499,6 +2634,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2584,7 +2743,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -2614,13 +2773,44 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -2893,6 +3083,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -2997,7 +3218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "bytes", "http", "http-body", @@ -3047,7 +3268,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -3382,7 +3603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -3395,7 +3616,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", "semver", ] @@ -3459,6 +3680,41 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -3676,7 +3932,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.14.0", "prettyplease", "syn", "wasm-metadata", @@ -3707,7 +3963,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -3726,7 +3982,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", "semver", "serde", diff --git a/Cargo.toml b/Cargo.toml index 240089e2..ccb240d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,12 @@ libdd-common = { version = "4.2.0", default-features = false } libdd-tinybytes = { version = "1.1.1", default-features = false } libdd-library-config = { version = "2.0.0", default-features = false } libdd-sampling = { version = "4.0.0", default-features = false } +# libdd-remote-config is not yet published to crates.io. Tracked via the +# [patch.crates-io] section below for any libdd-* crate co-resolved between +# crates.io and this git revision. +libdd-remote-config = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80", default-features = false, features = [ + "client", +] } opentelemetry_sdk = { version = "0.31.0", features = [ "trace", "metrics", @@ -62,6 +68,19 @@ hashbrown = { version = "0.15.5" } foldhash = { version = "0.1.5" } +# libdd-remote-config depends on libdd-common and (optionally) libdd-trace-protobuf +# via path+version. Redirect their crates.io copies to the same libdatadog revision +# so cargo unifies the graph (otherwise we get duplicate libdd-common copies and +# type-mismatch errors at the boundary). Remove once libdd-remote-config is on +# crates.io with matching libdd-* versions. +[patch.crates-io] +libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-telemetry = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-tinybytes = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } + [profile.dev] debug = 2 # full debug info diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 29461277..1e142c5c 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -1,6 +1,7 @@ Component,Origin,License,Copyright aho-corasick,https://github.com/BurntSushi/aho-corasick,Unlicense OR MIT,Andrew Gallant allocator-api2,https://github.com/zakarumych/allocator-api2,MIT OR Apache-2.0,Zakarum +android_system_properties,https://github.com/nical/android_system_properties,MIT OR Apache-2.0,Nicolas Silva anes,https://github.com/zrzka/anes-rs,MIT OR Apache-2.0,Robert Vojta ansi_term,https://github.com/ogham/rust-ansi-term,MIT,"ogham@bsago.me, Ryan Scheel (Havvy) , Josh Triplett " anstream,https://github.com/rust-cli/anstyle,MIT OR Apache-2.0,The anstream Authors @@ -15,7 +16,6 @@ async-lock,https://github.com/smol-rs/async-lock,Apache-2.0 OR MIT,Stjepan Glavi async-object-pool,https://github.com/alexliesenfeld/async-object-pool,MIT,Alexander Liesenfeld async-trait,https://github.com/dtolnay/async-trait,MIT OR Apache-2.0,David Tolnay atomic-waker,https://github.com/smol-rs/atomic-waker,Apache-2.0 OR MIT,"Stjepan Glavina , Contributors to futures-rs" -base64,https://github.com/marshallpierce/rust-base64,MIT OR Apache-2.0,"Alice Maz , Marshall Pierce " base64,https://github.com/marshallpierce/rust-base64,MIT OR Apache-2.0,Marshall Pierce bitflags,https://github.com/bitflags/bitflags,MIT OR Apache-2.0,The Rust Project Developers block-buffer,https://github.com/RustCrypto/utils,MIT OR Apache-2.0,RustCrypto Developers @@ -53,9 +53,14 @@ crossbeam-epoch,https://github.com/crossbeam-rs/crossbeam,MIT OR Apache-2.0,The crossbeam-utils,https://github.com/crossbeam-rs/crossbeam,MIT OR Apache-2.0,The crossbeam-utils Authors crunchy,https://github.com/AtheMathmo/crunchy,MIT,Eira Fransham crypto-common,https://github.com/RustCrypto/traits,MIT OR Apache-2.0,RustCrypto Developers +darling,https://github.com/TedDriggs/darling,MIT,Ted Driggs +darling_core,https://github.com/TedDriggs/darling,MIT,Ted Driggs +darling_macro,https://github.com/TedDriggs/darling,MIT,Ted Driggs +deranged,https://github.com/jhpratt/deranged,MIT OR Apache-2.0,Jacob Pratt diff,https://github.com/utkarshkukreti/diff.rs,MIT OR Apache-2.0,Utkarsh Kukreti digest,https://github.com/RustCrypto/traits,MIT OR Apache-2.0,RustCrypto Developers displaydoc,https://github.com/yaahc/displaydoc,MIT OR Apache-2.0,Jane Lusby +dyn-clone,https://github.com/dtolnay/dyn-clone,MIT OR Apache-2.0,David Tolnay either,https://github.com/rayon-rs/either,MIT OR Apache-2.0,bluss equivalent,https://github.com/indexmap-rs/equivalent,Apache-2.0 OR MIT,The equivalent Authors errno,https://github.com/lambda-fairy/rust-errno,MIT OR Apache-2.0,"Chris Wong , Dan Gohman " @@ -96,6 +101,8 @@ hyper,https://github.com/hyperium/hyper,MIT,Sean McArthur hyper-rustls,https://github.com/rustls/hyper-rustls,Apache-2.0 OR ISC OR MIT,The hyper-rustls Authors hyper-timeout,https://github.com/hjr3/hyper-timeout,MIT OR Apache-2.0,Herman J. Radtke III hyper-util,https://github.com/hyperium/hyper-util,MIT,Sean McArthur +iana-time-zone,https://github.com/strawlab/iana-time-zone,MIT OR Apache-2.0,"Andrew Straw , René Kijewski , Ryan Lopopolo " +iana-time-zone-haiku,https://github.com/strawlab/iana-time-zone,MIT OR Apache-2.0,René Kijewski icu_collections,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers icu_locale_core,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers icu_normalizer,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers @@ -104,8 +111,10 @@ icu_properties,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Projec icu_properties_data,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers icu_provider,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers id-arena,https://github.com/fitzgen/id-arena,MIT OR Apache-2.0,"Nick Fitzgerald , Aleksey Kladov " +ident_case,https://github.com/TedDriggs/ident_case,MIT OR Apache-2.0,Ted Driggs idna,https://github.com/servo/rust-url,MIT OR Apache-2.0,The rust-url developers idna_adapter,https://github.com/hsivonen/idna_adapter,Apache-2.0 OR MIT,The rust-url developers +indexmap,https://github.com/bluss/indexmap,Apache-2.0 OR MIT,The indexmap Authors indexmap,https://github.com/indexmap-rs/indexmap,Apache-2.0 OR MIT,The indexmap Authors ipnet,https://github.com/krisprice/ipnet,MIT OR Apache-2.0,Kris Price iri-string,https://github.com/lo48576/iri-string,MIT OR Apache-2.0,YOSHIOKA Takuma @@ -126,6 +135,7 @@ libdd-data-pipeline,https://github.com/DataDog/libdatadog/tree/main/libdd-data-p libdd-ddsketch,https://github.com/DataDog/libdatadog/tree/main/libdd-ddsketch,Apache-2.0,The libdd-ddsketch Authors libdd-dogstatsd-client,https://github.com/DataDog/libdatadog/tree/main/libdd-dogstatsd-client,Apache-2.0,The libdd-dogstatsd-client Authors libdd-library-config,https://github.com/DataDog/libdatadog/tree/main/libdd-library-config,Apache-2.0,The libdd-library-config Authors +libdd-remote-config,https://github.com/DataDog/libdatadog/tree/main/libdd-remote-config,Apache-2.0,Datadog Inc. libdd-sampling,https://github.com/DataDog/libdatadog/tree/main/libdd-sampling,Apache-2.0,Datadog Inc. libdd-shared-runtime,https://github.com/DataDog/libdatadog/tree/main/libdd-shared-runtime,Apache-2.0,The libdd-shared-runtime Authors libdd-telemetry,https://github.com/DataDog/libdatadog/tree/main/libdd-telemetry,Apache-2.0,The libdd-telemetry Authors @@ -141,6 +151,7 @@ lock_api,https://github.com/Amanieu/parking_lot,MIT OR Apache-2.0,Amanieu d'Antr log,https://github.com/rust-lang/log,MIT OR Apache-2.0,The Rust Project Developers lru,https://github.com/jeromefroe/lru-rs,MIT,Jerome Froelich lru-slab,https://github.com/Ralith/lru-slab,MIT OR Apache-2.0 OR Zlib,Benjamin Saunders +manual_future,https://github.com/dmarcuse/manual_future,MIT,Dana Marcuse matchers,https://github.com/hawkw/matchers,MIT,Eliza Weisman memchr,https://github.com/BurntSushi/memchr,Unlicense OR MIT,"Andrew Gallant , bluss" memfd,https://github.com/lucab/memfd-rs,MIT OR Apache-2.0,"Luca Bruno , Simonas Kazlauskas " @@ -148,6 +159,7 @@ mime,https://github.com/hyperium/mime,MIT OR Apache-2.0,Sean McArthur , Thomas de Zeeuw , Tokio Contributors " nix,https://github.com/nix-rust/nix,MIT,The nix-rust Project Developers nu-ansi-term,https://github.com/nushell/nu-ansi-term,MIT,"ogham@bsago.me, Ryan Scheel (Havvy) , Josh Triplett , The Nushell Project Developers" +num-conv,https://github.com/jhpratt/num-conv,MIT OR Apache-2.0,Jacob Pratt num-traits,https://github.com/rust-num/num-traits,MIT OR Apache-2.0,The Rust Project Developers once_cell,https://github.com/matklad/once_cell,MIT OR Apache-2.0,Aleksey Kladov once_cell_polyfill,https://github.com/polyfill-rs/once_cell_polyfill,MIT OR Apache-2.0,The once_cell_polyfill Authors @@ -174,6 +186,7 @@ plotters,https://github.com/plotters-rs/plotters,MIT,Hao Hou plotters-svg,https://github.com/plotters-rs/plotters,MIT,Hao Hou potential_utf,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers +powerfmt,https://github.com/jhpratt/powerfmt,MIT OR Apache-2.0,Jacob Pratt ppv-lite86,https://github.com/cryptocorrosion/cryptocorrosion,MIT OR Apache-2.0,The CryptoCorrosion Contributors prettyplease,https://github.com/dtolnay/prettyplease,MIT OR Apache-2.0,David Tolnay proc-macro2,https://github.com/dtolnay/proc-macro2,MIT OR Apache-2.0,"David Tolnay , Alex Crichton " @@ -214,6 +227,7 @@ rustversion,https://github.com/dtolnay/rustversion,MIT OR Apache-2.0,David Tolna ryu,https://github.com/dtolnay/ryu,Apache-2.0 OR BSL-1.0,David Tolnay same-file,https://github.com/BurntSushi/same-file,Unlicense OR MIT,Andrew Gallant schannel,https://github.com/steffengy/schannel-rs,MIT,"Steven Fackler , Steffen Butzer " +schemars,https://github.com/GREsau/schemars,MIT,Graham Esau scopeguard,https://github.com/bluss/scopeguard,MIT OR Apache-2.0,bluss security-framework,https://github.com/kornelski/rust-security-framework,MIT OR Apache-2.0,"Steven Fackler , Kornel " security-framework-sys,https://github.com/kornelski/rust-security-framework,MIT OR Apache-2.0,"Steven Fackler , Kornel " @@ -225,6 +239,8 @@ serde_derive,https://github.com/serde-rs/serde,MIT OR Apache-2.0,"Erick Tryzelaa serde_json,https://github.com/serde-rs/json,MIT OR Apache-2.0,"Erick Tryzelaar , David Tolnay " serde_regex,https://github.com/tailhook/serde-regex,MIT OR Apache-2.0,paul@colomiets.name serde_urlencoded,https://github.com/nox/serde_urlencoded,MIT OR Apache-2.0,Anthony Ramine +serde_with,https://github.com/jonasbb/serde_with,MIT OR Apache-2.0,"Jonas Bushart, Marcin Kaźmierczak" +serde_with_macros,https://github.com/jonasbb/serde_with,MIT OR Apache-2.0,Jonas Bushart serde_yaml,https://github.com/dtolnay/serde-yaml,MIT OR Apache-2.0,David Tolnay sha1,https://github.com/RustCrypto/hashes,MIT OR Apache-2.0,RustCrypto Developers sha2,https://github.com/RustCrypto/hashes,MIT OR Apache-2.0,RustCrypto Developers @@ -252,6 +268,9 @@ test-case-macros,https://github.com/frondeus/test-case,MIT,"Marcin Sas-Szymanski thiserror,https://github.com/dtolnay/thiserror,MIT OR Apache-2.0,David Tolnay thiserror-impl,https://github.com/dtolnay/thiserror,MIT OR Apache-2.0,David Tolnay thread_local,https://github.com/Amanieu/thread_local-rs,MIT OR Apache-2.0,Amanieu d'Antras +time,https://github.com/time-rs/time,MIT OR Apache-2.0,"Jacob Pratt , Time contributors" +time-core,https://github.com/time-rs/time,MIT OR Apache-2.0,"Jacob Pratt , Time contributors" +time-macros,https://github.com/time-rs/time,MIT OR Apache-2.0,"Jacob Pratt , Time contributors" tinystr,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers tinytemplate,https://github.com/bheisler/TinyTemplate,Apache-2.0 OR MIT,Brook Heisler tinyvec,https://github.com/Lokathor/tinyvec,Zlib OR Apache-2.0 OR MIT,Lokathor @@ -306,6 +325,9 @@ winapi-i686-pc-windows-gnu,https://github.com/retep998/winapi-rs,MIT OR Apache-2 winapi-util,https://github.com/BurntSushi/winapi-util,Unlicense OR MIT,Andrew Gallant winapi-x86_64-pc-windows-gnu,https://github.com/retep998/winapi-rs,MIT OR Apache-2.0,Peter Atashian windows,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,Microsoft +windows-core,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-core Authors +windows-implement,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-implement Authors +windows-interface,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-interface Authors windows-link,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-link Authors windows-registry,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-registry Authors windows-result,https://github.com/microsoft/windows-rs,MIT OR Apache-2.0,The windows-result Authors diff --git a/datadog-opentelemetry/Cargo.toml b/datadog-opentelemetry/Cargo.toml index a52ec5f3..c4296130 100644 --- a/datadog-opentelemetry/Cargo.toml +++ b/datadog-opentelemetry/Cargo.toml @@ -40,17 +40,8 @@ thiserror = { version = "1.0", default-features = false } anyhow = "1.0.97" rustc_version_runtime = "0.3.0" hyper = { version = "1.6", features = ["client", "http1"] } -hyper-util = { version = "0.1.16", features = [ - "client", - "client-legacy", - "http1", - "tokio", -] } -http-body-util = "0.1" tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } tokio-util = "0.7" -base64 = "0.21" -sha2 = "0.10" uuid = { version = "1.11.0", features = ["v4"] } arc-swap = "1.7.1" @@ -65,6 +56,7 @@ libdd-telemetry = { workspace = true } libdd-common = { workspace = true } libdd-tinybytes = { workspace = true } libdd-sampling = { workspace = true } +libdd-remote-config = { workspace = true } percent-encoding = "2.3.1" [target.'cfg(target_os="linux")'.dependencies] @@ -119,6 +111,7 @@ https = [ "libdd-trace-utils/https", "libdd-telemetry/https", "libdd-common/https", + "libdd-remote-config/https", "opentelemetry-otlp?/reqwest-rustls", "opentelemetry-otlp?/tls-roots", ] diff --git a/datadog-opentelemetry/src/core/configuration/remote_config.rs b/datadog-opentelemetry/src/core/configuration/remote_config.rs index 923cbd41..4de8568b 100644 --- a/datadog-opentelemetry/src/core/configuration/remote_config.rs +++ b/datadog-opentelemetry/src/core/configuration/remote_config.rs @@ -1,186 +1,37 @@ // Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +//! Remote Configuration client. +//! +//! Drives [`libdd_remote_config::fetch::SingleChangesFetcher`] from a dedicated +//! std thread running a single-threaded Tokio runtime, parses delivered files +//! via a custom [`ApmTracingConfig`] parser (registered through +//! [`RemoteConfigContent`]), and routes parsed payloads into +//! [`Config::update_sampling_rules_from_remote`] / [`Config::clear_remote_sampling_rules`]. + use crate::core::configuration::Config; use crate::core::utils::{ShutdownSignaler, WorkerHandle}; use anyhow::Result; use core::fmt; -use libdd_common::http_common::{self}; -use libdd_common::{connector::Connector::Http, Endpoint, HttpClient}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use libdd_common::tag::Tag; +use libdd_common::Endpoint; +use libdd_remote_config::fetch::{ + ConfigApplyState, ConfigInvariants, ConfigOptions, SingleChangesFetcher, +}; +use libdd_remote_config::file_change_tracker::Change; +use libdd_remote_config::file_storage::ParsedFileStorage; +use libdd_remote_config::parse::{ + ParseError, ParserRegistry, RemoteConfigContent, RemoteConfigParsed, +}; +use libdd_remote_config::{RemoteConfigCapabilities, RemoteConfigProduct, Target}; +use serde::Deserialize; +use std::sync::Arc; use std::thread::{self}; -use std::time::{Duration, Instant}; - -// HTTP client imports -use http_body_util::BodyExt; -use hyper::Method; -use hyper_util::client::legacy::{connect::HttpConnector, Client}; -use hyper_util::rt::TokioExecutor; - -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); // lowest timeout with no failures - -/// Capabilities that the client supports -#[derive(Debug, Clone)] -struct ClientCapabilities(u64); - -impl ClientCapabilities { - /// APM_TRACING_SAMPLE_RATE — bit 12. Tells the backend the tracer - /// honors RC's `tracing_sampling_rate` (global rate). Without this, - /// Datadog's APM Sampling UI marks the service as "No remotely - /// configurable tracer detected" for rate-only configs even when - /// the tracer-side logic is fully wired up. - const APM_TRACING_SAMPLE_RATE: u64 = 1 << 12; - - /// APM_TRACING_SAMPLE_RULES — bit 29. Tells the backend the tracer - /// honors RC's `tracing_sampling_rules`. - const APM_TRACING_SAMPLE_RULES: u64 = 1 << 29; - - fn new() -> Self { - Self(Self::APM_TRACING_SAMPLE_RATE | Self::APM_TRACING_SAMPLE_RULES) - } - - /// Encode capabilities as base64 string - fn encode(&self) -> String { - use base64::Engine; - let bytes = self.0.to_be_bytes(); - // Find first non-zero byte to minimize encoding size - let start = bytes - .iter() - .position(|&b| b != 0) - .unwrap_or(bytes.len() - 1); - base64::engine::general_purpose::STANDARD.encode(&bytes[start..]) - } -} - -/// Client state sent to the agent -#[derive(Debug, Clone, Serialize)] -struct ClientState { - /// Root version of the configuration - root_version: u64, - /// Versions of individual targets - targets_version: u64, - /// Configuration states - config_states: Vec, - /// Whether the client has an error - has_error: bool, - /// Error message if any - #[serde(skip_serializing_if = "Option::is_none")] - error: Option, - /// Backend client state (opaque string from server) - #[serde(skip_serializing_if = "Option::is_none")] - backend_client_state: Option, -} - -#[derive(Debug, Clone, Serialize)] -struct ConfigState { - /// ID of the configuration - id: String, - /// Version of the configuration - version: u64, - /// Product that owns this config - product: String, - /// Hash of the applied config - apply_state: u64, - /// Error if any while applying - apply_error: Option, -} - -/// Request sent to get configuration -#[derive(Debug, Serialize)] -struct ConfigRequest { - /// Client information - client: ClientInfo, - /// Cached target files - cached_target_files: Vec, -} - -#[derive(Debug, Serialize)] -struct ClientInfo { - /// State of the client - #[serde(skip_serializing_if = "Option::is_none")] - state: Option, - /// Client ID (runtime ID) - id: String, - /// Products this client is interested in - products: Vec, - /// Is this a tracer client - is_tracer: bool, - /// Tracer specific info - #[serde(skip_serializing_if = "Option::is_none")] - client_tracer: Option, - /// Client capabilities (base64 encoded) - capabilities: String, -} - -#[derive(Debug, Serialize)] -struct ClientTracer { - /// Runtime ID - runtime_id: String, - /// Language (rust) - language: String, - /// Tracer version - tracer_version: String, - /// Service name - service: String, - /// Additional services this tracer is monitoring - #[serde(default)] - extra_services: Vec, - /// Environment - #[serde(skip_serializing_if = "Option::is_none")] - env: Option, - /// App version - #[serde(skip_serializing_if = "Option::is_none")] - app_version: Option, - /// Global tags - tags: Vec, -} - -#[derive(Debug, Clone, Serialize)] -struct CachedTargetFile { - /// Path of the target file - path: String, - /// Length of the file - length: u64, - /// Hashes of the file - hashes: Vec, -} - -#[derive(Debug, Clone, Serialize)] -struct Hash { - /// Algorithm used (e.g., "sha256") - algorithm: String, - /// Hash value - hash: String, -} - -/// Response from the configuration endpoint -#[derive(Debug, Deserialize)] -struct ConfigResponse { - /// Root metadata (TUF roots) - base64 encoded - #[serde(default)] - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - roots: Option>, - /// Targets metadata - base64 encoded JSON - #[serde(default)] - targets: Option, - /// Target files containing actual config data - #[serde(default)] - target_files: Option>, - /// Client configs to apply - #[serde(default)] - client_configs: Option>, -} +use std::time::Duration; -#[derive(Debug, Deserialize)] -struct TargetFile { - /// Path of the file - path: String, - /// Raw content (base64 encoded in responses) - raw: String, -} +/// HTTP timeout for a single RC fetch. Matches the historical in-tree client. +const FETCH_TIMEOUT_MS: u64 = 3_000; // Custom deserializer that preserves explicit null as Some(Value::Null) fn missing_field_and_null_value<'de, D>( @@ -189,15 +40,15 @@ fn missing_field_and_null_value<'de, D>( where D: serde::Deserializer<'de>, { - // Deserialize as Value directly, which preserves null Ok(Some(serde_json::Value::deserialize(deserializer)?)) } -/// Configuration payload for APM tracing -/// Based on the apm-tracing.json schema from dd-go +/// Configuration payload for APM tracing. +/// +/// Based on the apm-tracing.json schema from dd-go. /// See: https://github.com/DataDog/dd-go/blob/prod/remote-config/apps/rc-schema-validation/schemas/apm-tracing.json #[derive(Debug, Clone, Deserialize)] -struct ApmTracingConfig { +pub(crate) struct ApmTracingConfig { id: String, lib_config: LibConfig, // lib_config is a required property /// The service/env this config targets. The backend RC predicate already @@ -222,6 +73,9 @@ struct ServiceTarget { #[derive(Debug, Clone, Deserialize)] struct LibConfig { + /// `None` = field absent (no change intended). + /// `Some(Value::Null)` = explicit null (clear the override). + /// `Some(Value::Array(_))` = concrete rule list. #[serde( deserialize_with = "missing_field_and_null_value", default, @@ -230,9 +84,9 @@ struct LibConfig { tracing_sampling_rules: Option, /// Global trace sample rate (0.0–1.0) pushed via Remote Config. - /// `None` means the field was absent (no change intended). - /// `Some(Value::Null)` means the field was explicitly `null` (clear the override). - /// `Some(Value::Number)` means a concrete rate was provided. + /// `None` = field absent (no change intended). + /// `Some(Value::Null)` = explicit null (clear the override). + /// `Some(Value::Number)` = concrete rate. #[serde( deserialize_with = "missing_field_and_null_value", default, @@ -241,50 +95,172 @@ struct LibConfig { tracing_sampling_rate: Option, } -/// TUF targets metadata -/// This is just an alias for SignedTargets to match the JSON structure -type TargetsMetadata = SignedTargets; - -#[derive(Debug, Deserialize, Serialize)] -struct TargetDesc { - /// Length of the target file - length: u64, - /// Hashes of the target file (algorithm -> hash) - hashes: HashMap, - /// Custom metadata for this target - custom: Option, -} +impl RemoteConfigContent for ApmTracingConfig { + const PRODUCT: RemoteConfigProduct = RemoteConfigProduct::ApmTracing; -#[derive(Debug, Deserialize)] -struct Targets { - /// Type of the targets (usually "targets") - #[serde(rename = "_type")] - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - target_type: String, - /// Custom metadata - custom: Option, - /// Expiration time - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - expires: String, - /// Specification version - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - spec_version: String, - /// Target descriptions (path -> TargetDesc) - targets: HashMap, - /// Version of the targets - version: u64, + fn parse(data: &[u8]) -> std::result::Result { + Ok(serde_json::from_slice(data)?) + } } -#[derive(Debug, Deserialize)] -struct SignedTargets { - /// Signatures (we don't validate these currently) - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - signatures: Option>, - /// The signed targets data - signed: Targets, - /// Version of the signed targets - #[allow(dead_code)] // Part of TUF specification but not used in current implementation - version: Option, +/// Apply a parsed APM_TRACING config to the tracer's sampling state. +/// +/// Returns `Err` on malformed payloads (out-of-range rate, non-numeric rate, +/// non-array rules, or downstream rule-rejection by libdd-sampling) so the +/// caller can report `ConfigApplyState::Error` to the agent and leave any +/// prior remote override in place. +fn apply_apm_tracing(tracing_config: &ApmTracingConfig, config: &Arc) -> Result<()> { + // Defense-in-depth target check: only apply a config whose service_target + // matches this tracer's service/env. The backend predicate already scopes + // delivery, but a stale or mistargeted payload must never install another + // service's/env's policy. A `*` (or absent) component applies regardless. + // Mismatch => ignore (no sampler mutation), mirroring dd-trace-py. + if let Some(target) = &tracing_config.service_target { + if let Some(svc) = target.service.as_deref() { + if svc != "*" && !config.rc_service_target_matches(svc) { + crate::dd_debug!( + "RemoteConfigClient: ignoring APM_TRACING config targeting service {:?} (not this tracer's service or extra services)", + svc + ); + return Ok(()); + } + } + if let Some(target_env) = target.env.as_deref() { + let tracer_env = config.env().unwrap_or(""); + if target_env != "*" && !target_env.eq_ignore_ascii_case(tracer_env) { + crate::dd_debug!( + "RemoteConfigClient: ignoring APM_TRACING config targeting env {:?} (tracer env is {:?})", + target_env, + tracer_env + ); + return Ok(()); + } + } + } + + let lib = &tracing_config.lib_config; + + let any_field_present = + lib.tracing_sampling_rules.is_some() || lib.tracing_sampling_rate.is_some(); + + // tracing_sampling_rate must be either null (clear) or a JSON number. + // Any other present-but-non-numeric value (string, bool, object) is a + // malformed payload — reject rather than silently treating it as a + // clear, which would wipe an active remote sampling policy. + let rate: Option = match &lib.tracing_sampling_rate { + None | Some(serde_json::Value::Null) => None, + Some(serde_json::Value::Number(n)) => match n.as_f64() { + Some(r) if r.is_finite() && (0.0..=1.0).contains(&r) => Some(r), + Some(r) => { + return Err(anyhow::anyhow!( + "tracing_sampling_rate must be in [0.0, 1.0], got: {}", + r + )); + } + None => { + return Err(anyhow::anyhow!( + "tracing_sampling_rate is not representable as f64" + )); + } + }, + Some(other) => { + return Err(anyhow::anyhow!( + "tracing_sampling_rate must be a JSON number or null, got: {}", + other + )); + } + }; + let rules_value = match &lib.tracing_sampling_rules { + Some(v) if !v.is_null() => Some(v.clone()), + _ => None, + }; + + match (rules_value, rate) { + (None, None) => { + if any_field_present { + crate::dd_debug!( + "RemoteConfigClient: APM tracing config received with null sampling fields, clearing remote override" + ); + config.clear_remote_sampling_rules(Some(tracing_config.id.clone())); + } else { + crate::dd_debug!( + "RemoteConfigClient: APM tracing config received but no tracing_sampling_rules or tracing_sampling_rate present" + ); + } + } + (rules_opt, rate_opt) => { + // An explicit empty `tracing_sampling_rules: []` is treated the + // same as null/absent: RC has no rules to deliver, so env-side + // rules survive. Operators clear remote rules by sending + // `tracing_sampling_rules: null` (the conventional RC clear); + // an empty array is an unusual edge case and the lenient + // interpretation is safer than wiping env config silently. + let rc_has_explicit_rules = matches!( + rules_opt, + Some(serde_json::Value::Array(ref arr)) if !arr.is_empty() + ); + + let mut rules: Vec = match rules_opt { + Some(serde_json::Value::Array(arr)) => arr, + Some(other) => { + return Err(anyhow::anyhow!( + "tracing_sampling_rules must be a JSON array, got: {}", + other + )); + } + None => Vec::new(), + }; + + // Multi-source precedence: + // - If RC delivered explicit rules, env rules are replaced. + // - If RC delivered only a rate, env rules survive and apply in front of the synthetic + // catch-all. + if !rc_has_explicit_rules { + let env_rules = config.local_trace_sampling_rules(); + if !env_rules.is_empty() { + let env_json = serde_json::to_value(&*env_rules).map_err(|e| { + anyhow::anyhow!("Failed to serialize env sampling rules: {}", e) + })?; + let serde_json::Value::Array(env_arr) = env_json else { + return Err(anyhow::anyhow!( + "BUG: serialized env sampling rules are not a JSON array" + )); + }; + let mut composed = env_arr; + composed.append(&mut rules); + rules = composed; + } + } + + // Effective catch-all rate: RC rate wins; otherwise fall back + // to DD_TRACE_SAMPLE_RATE if it's set (Option distinguishes + // unset from explicit 1.0). + let env_rate = config.trace_sample_rate(); + let catch_all_rate: Option = match rate_opt { + Some(r) => Some(r), + None => env_rate.filter(|r| r.is_finite()), + }; + if let Some(r) = catch_all_rate { + // The global RC rate is a "local-user-like" fallback: it must + // produce DM "-3" (LOCAL_USER), not "-12" (REMOTE_DYNAMIC). + // Omit `provenance`; libdd-sampling deserializes it as + // "default" via its serde default, which maps to DM -3. + rules.push(serde_json::json!({ "sample_rate": r })); + } + + let rules_json = serde_json::to_string(&serde_json::Value::Array(rules)) + .map_err(|e| anyhow::anyhow!("Failed to serialize sampling rules: {}", e))?; + + config + .update_sampling_rules_from_remote(&rules_json, Some(tracing_config.id.clone())) + .map_err(|e| { + anyhow::anyhow!("Failed to update sampling rules from remote: {}", e) + })?; + crate::dd_debug!("RemoteConfigClient: Applied sampling rules from remote config"); + } + } + + Ok(()) } #[derive(Debug, Clone)] @@ -336,10 +312,10 @@ impl RemoteConfigClientHandle { } } -/// Receiver for shutdown signals through the cancellation token +/// Receiver for shutdown signals through the cancellation token. /// -/// When this struct is dropped, it will signal that the shutdown is finished to the -/// handle +/// When this struct is dropped, it signals that the worker has finished to the +/// matching [`WorkerHandle`]. struct RemoteConfigClientShutdownReceiver { cancel_token: tokio_util::sync::CancellationToken, shutdown_finished: Arc, @@ -352,12 +328,14 @@ impl Drop for RemoteConfigClientShutdownReceiver { } pub struct RemoteConfigClientWorker { - client: RemoteConfigClient, + config: Arc, + endpoint: Endpoint, shutdown_receiver: RemoteConfigClientShutdownReceiver, } impl RemoteConfigClientWorker { pub fn start(config: Arc) -> Result { + let endpoint = build_endpoint(&config)?; let cancel_token = tokio_util::sync::CancellationToken::new(); let shutdown_finished = ShutdownSignaler::new(); let shutdown_receiver = RemoteConfigClientShutdownReceiver { @@ -365,7 +343,8 @@ impl RemoteConfigClientWorker { shutdown_finished: shutdown_finished.clone(), }; let worker = Self { - client: RemoteConfigClient::new(config)?, + config, + endpoint, shutdown_receiver, }; let join_handle = thread::spawn(move || worker.run()); @@ -375,10 +354,9 @@ impl RemoteConfigClientWorker { }) } - fn run(mut self) { + fn run(self) { crate::dd_debug!("RemoteConfigClient: started client worker"); - // Create Tokio runtime in the background thread let rt = match tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -390,656 +368,154 @@ impl RemoteConfigClientWorker { } }; - let run_loop = async { - let mut last_poll = Instant::now(); - - loop { - // Fetch and apply configuration - match self.client.fetch_and_apply_config().await { - Ok(()) => { - crate::dd_debug!( - "RemoteConfigClient: Successfully fetched and applied config" - ); - // Clear any previous errors - if let Ok(mut state) = self.client.state.lock() { - state.has_error = false; - state.error = None; - } - } - Err(e) => { - crate::dd_debug!("RemoteConfigClient: Failed to fetch config: {}", e); - // Record error in state - if let Ok(mut state) = self.client.state.lock() { - state.has_error = true; - state.error = Some(format!("{e}")); - } - } - } - - // Wait for next poll interval - let elapsed = last_poll.elapsed(); - if elapsed < self.client.poll_interval { - tokio::time::sleep(self.client.poll_interval - elapsed).await - } - last_poll = Instant::now(); - } - }; - - rt.block_on(async { - tokio::select! { - _ = self.shutdown_receiver.cancel_token.cancelled() => {}, - _ = run_loop => {}, - } - }); - } -} - -/// Remote configuration client that polls the Datadog Agent for configuration updates. -/// -/// This client is responsible for: -/// - Fetching remote configuration from the Datadog Agent -/// - Processing APM_TRACING product updates (specifically sampling rules) -/// - Maintaining client state and capabilities -/// - Providing a callback mechanism for configuration updates -/// -/// The client currently handles a single product type (APM_TRACING) -/// that defines sampling rules. -struct RemoteConfigClient { - /// Unique identifier for this client instance - /// Different from runtime_id - each RemoteConfigClient gets its own UUID - client_id: String, - config: Arc, - agent_endpoint: Endpoint, - state: Arc>, - capabilities: ClientCapabilities, - poll_interval: Duration, - // Cache of successfully applied configurations - cached_target_files: Vec, - // Registry of product handlers for processing different config types - product_registry: ProductRegistry, - // default http client - http_client: HttpClient, -} - -impl RemoteConfigClient { - /// Creates a new remote configuration client - pub fn new(config: Arc) -> Result { - let agent_url = libdd_common::parse_uri(&config.trace_agent_url()) - .map_err(|_| RemoteConfigClientError::InvalidAgentUri)?; - let mut parts = agent_url.into_parts(); - parts.path_and_query = Some( - "/v0.7/config" - .parse() - .map_err(|_| RemoteConfigClientError::InvalidAgentUri)?, - ); - let agent_url = - hyper::Uri::from_parts(parts).map_err(|_| RemoteConfigClientError::InvalidAgentUri)?; - - let agent_endpoint = libdd_common::Endpoint::from_url(agent_url); - - let state = Arc::new(Mutex::new(ClientState { - root_version: 1, // Agent requires >= 1 (base TUF director root) - targets_version: 0, - config_states: Vec::new(), - has_error: false, - error: None, - backend_client_state: None, - })); - - let poll_interval = Duration::from_secs_f64(config.remote_config_poll_interval()); - - // Create HTTP connector with timeout configuration - let mut connector = HttpConnector::new(); - connector.set_connect_timeout(Some(DEFAULT_TIMEOUT)); - - Ok(Self { - client_id: uuid::Uuid::new_v4().to_string(), - config, - agent_endpoint, - state, - capabilities: ClientCapabilities::new(), - poll_interval, - cached_target_files: Vec::new(), - product_registry: ProductRegistry::new(), - http_client: Client::builder(TokioExecutor::default()).build(Http(connector)), - }) + rt.block_on(self.run_loop()); } - /// Fetches configuration from the agent and applies it - async fn fetch_and_apply_config(&mut self) -> Result<()> { - let request_payload = self.build_request()?; - // Serialize the request to JSON - let json_body = serde_json::to_string(&request_payload) - .map_err(|e| anyhow::anyhow!("Failed to serialize request: {}", e))?; - - let req_builder = self - .agent_endpoint - .to_request_builder("dd-trace-rs") - .map_err(|e| anyhow::anyhow!("Failed to build request builder: {}", e))?; - - let req = req_builder - .method(Method::POST) - .header("content-type", "application/json") - .body(http_common::Body::from(json_body)) - .map_err(|e| anyhow::anyhow!("Failed to build request: {}", e))?; - - // Send request to agent - let response = self - .http_client - .request(req) - .await - .map_err(|e| anyhow::anyhow!("Failed to send request: {}", e))?; - - if !response.status().is_success() { - return Err(anyhow::anyhow!( - "Agent returned error status: {}", - response.status() - )); - } - - // Collect the response body - let body_bytes = response - .into_body() - .collect() - .await - .map_err(|e| anyhow::anyhow!("Failed to read response body: {}", e))? - .to_bytes(); - - // Parse JSON response - let config_response: ConfigResponse = serde_json::from_slice(&body_bytes) - .map_err(|e| anyhow::anyhow!("Failed to parse response: {}", e))?; - - // Process the configuration response - self.process_response(config_response)?; - - Ok(()) - } + async fn run_loop(self) { + let poll_interval = Duration::from_secs_f64(self.config.remote_config_poll_interval()); - /// Builds the configuration request - fn build_request(&self) -> Result { - let state = self - .state - .lock() - .map_err(|_| anyhow::anyhow!("Failed to lock state"))?; - - let config = &self.config; - - let client_info = ClientInfo { - state: Some(state.clone()), - id: self.client_id.clone(), - products: vec!["APM_TRACING".to_string()], - is_tracer: true, - client_tracer: Some(ClientTracer { - runtime_id: config.runtime_id().to_string(), - language: "rust".to_string(), - tracer_version: config.tracer_version().to_string(), - service: config.service().to_string(), - extra_services: config.get_extra_services(), - env: config.env().map(|s| s.to_string()), - app_version: config.version().map(|s| s.to_string()), - tags: config - .global_tags() - .map(|(key, value)| format!("{key}:{value}")) - .collect(), - }), - capabilities: self.capabilities.encode(), + let registry = match ParserRegistry::new().with::() { + Ok(r) => r, + Err(e) => { + crate::dd_debug!( + "RemoteConfigClient: failed to register ApmTracingConfig parser: {}", + e + ); + return; + } }; + let storage = ParsedFileStorage::with_registry(Arc::new(registry)); - let cached_files = self.cached_target_files.clone(); + let target = build_target(&self.config); + let runtime_id = self.config.runtime_id().to_string(); - Ok(ConfigRequest { - client: client_info, - cached_target_files: cached_files, - }) - } + let options = ConfigOptions { + invariants: ConfigInvariants { + language: self.config.language().to_string(), + tracer_version: self.config.tracer_version().to_string(), + endpoint: self.endpoint.clone(), + }, + products: vec![RemoteConfigProduct::ApmTracing], + capabilities: vec![ + RemoteConfigCapabilities::ApmTracingSampleRate, + RemoteConfigCapabilities::ApmTracingSampleRules, + ], + }; - /// Processes the configuration response - fn process_response(&mut self, response: ConfigResponse) -> Result<()> { - // Process targets metadata to update backend state and version - let mut path_to_custom: HashMap, Option)> = HashMap::new(); - let mut signed_targets: Option = None; - - if let Some(targets_str) = response.targets { - use base64::Engine; - let decoded = base64::engine::general_purpose::STANDARD - .decode(&targets_str) - .map_err(|e| anyhow::anyhow!("Failed to decode targets: {}", e))?; - - let targets_json = String::from_utf8(decoded) - .map_err(|e| anyhow::anyhow!("Invalid UTF-8 in targets: {}", e))?; - - let targets: TargetsMetadata = serde_json::from_str(&targets_json) - .map_err(|e| anyhow::anyhow!("Failed to parse targets metadata: {}", e))?; - - // Store signed targets for validation - let targets_map = targets - .signed - .targets - .iter() - .map(|(k, v)| (k.clone(), serde_json::to_value(v).unwrap())) - .collect(); - signed_targets = Some(serde_json::Value::Object(targets_map)); - - // Build lookup for per-path id and version from targets.signed.targets[*].custom - for (path, desc) in &targets.signed.targets { - let custom = &desc.custom; - let id = custom - .as_ref() - .and_then(|c| Some(c.get("id")?.as_str()?.to_owned())); - // Datadog RC uses custom.v (int). Fallback to custom.version if needed - let version: Option = custom - .as_ref() - .and_then(|c| c.get("v").or_else(|| c.get("version"))?.as_u64()); - path_to_custom.insert(path.clone(), (id, version)); - } + let mut fetcher = SingleChangesFetcher::new(storage, target, runtime_id, options); - // Update state with backend state and version - if let Ok(mut state) = self.state.lock() { - state.targets_version = targets.signed.version; + loop { + fetcher.set_extra_services(self.config.get_extra_services()); - if let Some(custom) = &targets.signed.custom { - if let Some(backend_state) = - custom.get("opaque_backend_state").and_then(|v| v.as_str()) - { - state.backend_client_state = Some(backend_state.to_string()); - } - } + match fetcher.fetch_changes().await { + Ok(changes) => apply_changes(changes, &self.config, &fetcher), + Err(e) => crate::dd_debug!("RemoteConfigClient: fetch failed: {}", e), } - } - - // Validate target files against signed targets and client configs - if let Some(target_files) = &response.target_files { - self.validate_signed_target_files( - target_files, - &signed_targets, - &response.client_configs, - )?; - } - // Parse target files if present - if let Some(target_files) = response.target_files { - // Build a new cache - let mut new_cache = Vec::new(); - let mut any_failure = false; - let mut config_states_cleared = false; - - for file in target_files { - // Extract product and config_id from path to determine which handler to use - // Path format is: ^(datadog/\d+|employee)/[^/]+/[^/]+/[^/]+$ - // Where the three last groups represent product/config_id/name - let Some((product, config_id)) = extract_product_and_id_from_path(&file.path) - else { - crate::dd_debug!( - "RemoteConfigClient: Failed to extract product from path: {}", - file.path - ); - continue; - }; - - // Check if we have a handler for this product - let handler = match self.product_registry.get_handler(&product) { - Some(h) => h, - None => { - continue; - } - }; - - // Target files contain base64 encoded JSON configs - use base64::Engine; - let decoded = base64::engine::general_purpose::STANDARD - .decode(&file.raw) - .map_err(|e| anyhow::anyhow!("Failed to decode config: {}", e))?; - - // Determine config id and version for state reporting (do this before applying) - let (_, meta_version) = path_to_custom - .get(&file.path) - .cloned() - .unwrap_or((None, None)); - let config_version = meta_version.unwrap_or(1); - - // Apply the config and record success or failure state - // Right now we only support APM_TRACING handler, but in the future we will support - // other products - match handler.process_config(&decoded, &self.config) { - Ok(_) => { - // Calculate SHA256 hash of the decoded file - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(&decoded); - let hash_result = hasher.finalize(); - let hash_hex = format!("{hash_result:x}"); - - new_cache.push(CachedTargetFile { - path: file.path.clone(), - length: decoded.len() as u64, - hashes: vec![Hash { - algorithm: "sha256".to_string(), - hash: hash_hex, - }], - }); - - // Update state to reflect successful application with accurate id/version - if let Ok(mut state) = self.state.lock() { - if !config_states_cleared { - state.config_states.clear(); - config_states_cleared = true; - } - state.config_states.push(ConfigState { - id: config_id, - version: config_version, - product: product.clone(), - apply_state: 2, // 2 denotes success - apply_error: None, - }); - } - } - Err(e) => { - any_failure = true; - crate::dd_debug!( - "RemoteConfigClient: Failed to apply {} config {}: {}", - product, - config_id, - e - ); - if let Ok(mut state) = self.state.lock() { - if !config_states_cleared { - state.config_states.clear(); - config_states_cleared = true; - } - // 3 denotes error - state.config_states.push(ConfigState { - id: config_id, - version: config_version, - product, - apply_state: 3, // 3 denotes error - apply_error: Some(format!("{e}")), - }); - } - // Do not add to cache on failure - continue; - } - } - } - - // Only update the cache if we successfully processed all configs - // This ensures we don't lose our previous cache state on errors - if !any_failure { - self.cached_target_files = new_cache; + tokio::select! { + _ = self.shutdown_receiver.cancel_token.cancelled() => break, + _ = tokio::time::sleep(poll_interval) => {} } } - - Ok(()) } +} - /// Validates that target files exist in either signed targets or client configs - fn validate_signed_target_files( - &self, - payload_target_files: &[TargetFile], - payload_targets_signed: &Option, - client_configs: &Option>, - ) -> Result<()> { - for target in payload_target_files { - let exists_in_signed_targets = payload_targets_signed - .as_ref() - .and_then(|targets| targets.get(&target.path)) - .is_some(); - - let exists_in_client_configs = client_configs - .as_ref() - .map(|configs| configs.contains(&target.path)) - .unwrap_or(false); - - if !exists_in_signed_targets && !exists_in_client_configs { - return Err(anyhow::anyhow!( - "target file {} not exists in client_config and signed targets", - target.path - )); - } - } +fn build_endpoint(config: &Config) -> Result { + let agent_url = libdd_common::parse_uri(&config.trace_agent_url()) + .map_err(|_| RemoteConfigClientError::InvalidAgentUri)?; + Ok(Endpoint { + url: agent_url, + api_key: None, + timeout_ms: FETCH_TIMEOUT_MS, + ..Default::default() + }) +} - Ok(()) +/// Translate `Config` identity (service / env / version / global tags) into the +/// [`Target`] used by `libdd-remote-config` for server-side scoping. +fn build_target(config: &Config) -> Target { + Target { + service: config.service().to_string(), + env: config.env().unwrap_or_default().to_string(), + app_version: config.version().unwrap_or_default().to_string(), + tags: convert_global_tags(config), + process_tags: vec![], } } -/// Product handler trait for processing different remote config products -/// Each product (APM_TRACING, AGENT_CONFIG, etc.) implements this trait to handle their specific -/// configuration format -trait ProductHandler { - /// Process the configuration for this product - fn process_config(&self, config_json: &[u8], config: &Arc) -> Result<()>; - - /// Get the product name this handler supports - fn product_name(&self) -> &'static str; +/// Convert the tracer's `global_tags` iterator into `libdd-common` [`Tag`] +/// values. +fn convert_global_tags(config: &Config) -> Vec { + config + .global_tags() + .filter_map(|(key, value)| match Tag::new(key, value) { + Ok(tag) => Some(tag), + Err(e) => { + crate::dd_debug!( + "RemoteConfigClient: skipping invalid global tag {key}:{value}: {e}" + ); + None + } + }) + .collect() } -struct ApmTracingHandler; - -impl ProductHandler for ApmTracingHandler { - fn process_config(&self, config_json: &[u8], config: &Arc) -> Result<()> { - let tracing_config: ApmTracingConfig = serde_json::from_slice(config_json) - .map_err(|e| anyhow::anyhow!("Failed to parse APM tracing config: {}", e))?; - - // Defense-in-depth target check: only apply a config whose service_target - // matches this tracer's service/env. The backend predicate already scopes - // delivery, but a stale or mistargeted payload must never install another - // service's/env's policy. A `*` (or absent) component applies regardless. - // Mismatch => ignore (no sampler mutation), mirroring dd-trace-py. - if let Some(target) = &tracing_config.service_target { - // Only skip a config whose target is specific (non-`*`) AND does not - // apply to this tracer. Service matches the primary or any advertised - // extra service; both service and env are compared case-insensitively - // (the UI warns service-name case can differ). Being lenient here is - // deliberate: the guard must skip only configs that are definitely - // for another service/env, never a valid one for us. - if let Some(svc) = target.service.as_deref() { - if svc != "*" && !config.rc_service_target_matches(svc) { - crate::dd_debug!( - "RemoteConfigClient: ignoring APM_TRACING config targeting service {:?} (not this tracer's service or extra services)", - svc - ); - return Ok(()); - } - } - if let Some(target_env) = target.env.as_deref() { - let tracer_env = config.env().unwrap_or(""); - if target_env != "*" && !target_env.eq_ignore_ascii_case(tracer_env) { - crate::dd_debug!( - "RemoteConfigClient: ignoring APM_TRACING config targeting env {:?} (tracer env is {:?})", - target_env, - tracer_env - ); - return Ok(()); - } - } +type StoredApmFile = + libdd_remote_config::file_storage::RawFile>>; + +/// Dispatcher invoked once per fetcher poll. Routes each [`Change`] to the +/// matching application path and reports the apply state back to the fetcher +/// so the agent learns about successful / failed application. +fn apply_changes( + changes: Vec, anyhow::Result>>>, + config: &Arc, + fetcher: &SingleChangesFetcher, +) { + for change in changes { + match change { + Change::Add(file) | Change::Update(file, _) => apply_one(&file, config, fetcher), + Change::Remove(file) => apply_remove(&file, config), } + } +} - let lib = tracing_config.lib_config; - - let any_field_present = - lib.tracing_sampling_rules.is_some() || lib.tracing_sampling_rate.is_some(); - - // tracing_sampling_rate must be either null (clear) or a JSON number. - // Any other present-but-non-numeric value (string, bool, object) is a - // malformed payload — reject rather than silently treating it as a - // clear, which would wipe an active remote sampling policy. - let rate: Option = match &lib.tracing_sampling_rate { - None | Some(serde_json::Value::Null) => None, - Some(serde_json::Value::Number(n)) => match n.as_f64() { - Some(r) if r.is_finite() && (0.0..=1.0).contains(&r) => Some(r), - Some(r) => { - return Err(anyhow::anyhow!( - "tracing_sampling_rate must be in [0.0, 1.0], got: {}", - r - )); - } - None => { - return Err(anyhow::anyhow!( - "tracing_sampling_rate is not representable as f64" - )); - } +fn apply_one( + file: &Arc, + config: &Arc, + fetcher: &SingleChangesFetcher, +) { + let contents = file.contents(); + let state = match &*contents { + Ok(Some(parsed)) => match parsed.downcast::() { + Some(cfg) => match apply_apm_tracing(cfg, config) { + Ok(()) => ConfigApplyState::Acknowledged, + Err(e) => ConfigApplyState::Error(e.to_string()), }, - Some(other) => { - return Err(anyhow::anyhow!( - "tracing_sampling_rate must be a JSON number or null, got: {}", - other - )); - } - }; - let rules_value = match lib.tracing_sampling_rules { - Some(v) if !v.is_null() => Some(v), - _ => None, - }; - - match (rules_value, rate) { - (None, None) => { - if any_field_present { - crate::dd_debug!( - "RemoteConfigClient: APM tracing config received with null sampling fields, clearing remote override" - ); - config.clear_remote_sampling_rules(Some(tracing_config.id)); - } else { - crate::dd_debug!( - "RemoteConfigClient: APM tracing config received but no tracing_sampling_rules or tracing_sampling_rate present" - ); - } - } - (rules_opt, rate_opt) => { - // An explicit empty `tracing_sampling_rules: []` is treated the - // same as null/absent: RC has no rules to deliver, so env-side - // rules survive. Operators clear remote rules by sending - // `tracing_sampling_rules: null` (the conventional RC clear); - // an empty array is an unusual edge case and the lenient - // interpretation is safer than wiping env config silently. - let rc_has_explicit_rules = matches!( - rules_opt, - Some(serde_json::Value::Array(ref arr)) if !arr.is_empty() + None => { + crate::dd_debug!( + "RemoteConfigClient: parsed file is not ApmTracingConfig, skipping" ); - - let mut rules: Vec = match rules_opt { - Some(serde_json::Value::Array(arr)) => arr, - Some(other) => { - return Err(anyhow::anyhow!( - "tracing_sampling_rules must be a JSON array, got: {}", - other - )); - } - None => Vec::new(), - }; - - // Multi-source precedence: - // - If RC delivered explicit rules, env rules are replaced. - // - If RC delivered only a rate, env rules survive and apply in front of the - // synthetic catch-all. - if !rc_has_explicit_rules { - let env_rules = config.local_trace_sampling_rules(); - if !env_rules.is_empty() { - let env_json = serde_json::to_value(&*env_rules).map_err(|e| { - anyhow::anyhow!("Failed to serialize env sampling rules: {}", e) - })?; - let serde_json::Value::Array(env_arr) = env_json else { - return Err(anyhow::anyhow!( - "BUG: serialized env sampling rules are not a JSON array" - )); - }; - let mut composed = env_arr; - composed.append(&mut rules); - rules = composed; - } - } - - // Effective catch-all rate: RC rate wins; otherwise fall back - // to DD_TRACE_SAMPLE_RATE if it's set (Option distinguishes - // unset from explicit 1.0). - let env_rate = config.trace_sample_rate(); - let catch_all_rate: Option = match rate_opt { - Some(r) => Some(r), - None => env_rate.filter(|r| r.is_finite()), - }; - if let Some(r) = catch_all_rate { - // The global RC rate is a "local-user-like" fallback: it must - // produce DM "-3" (LOCAL_USER), not "-12" (REMOTE_DYNAMIC). - // Omit `provenance`; libdd-sampling deserializes it as - // "default" via its serde default, which maps to DM -3. - rules.push(serde_json::json!({ "sample_rate": r })); - } - - let rules_json = serde_json::to_string(&serde_json::Value::Array(rules)) - .map_err(|e| anyhow::anyhow!("Failed to serialize sampling rules: {}", e))?; - - config - .update_sampling_rules_from_remote(&rules_json, Some(tracing_config.id)) - .map_err(|e| { - anyhow::anyhow!("Failed to update sampling rules from remote: {}", e) - })?; - crate::dd_debug!("RemoteConfigClient: Applied sampling rules from remote config"); + return; } + }, + Ok(None) => { + crate::dd_debug!("RemoteConfigClient: file has no parsed contents, skipping"); + return; } - - Ok(()) - } - - fn product_name(&self) -> &'static str { - "APM_TRACING" - } + Err(e) => ConfigApplyState::Error(e.to_string()), + }; + drop(contents); + fetcher.set_config_state(file, state); } -/// Product registry that maps product names to their handlers -/// This makes it easy to add new products without modifying the main processing logic -struct ProductRegistry { - handlers: HashMap>, -} - -impl ProductRegistry { - fn new() -> Self { - let mut registry = Self { - handlers: HashMap::new(), - }; - - // Register all supported products - registry.register(Box::new(ApmTracingHandler)); - - registry - } - - fn register(&mut self, handler: Box) { - self.handlers - .insert(handler.product_name().to_string(), handler); - } - - fn get_handler(&self, product: &str) -> Option<&(dyn ProductHandler + Send + Sync)> { - self.handlers.get(product).map(|handler| handler.as_ref()) - } -} - -/// Extract product and id from remote config path -/// Path format is: ^(datadog/\d+|employee)/[^/]+/[^/]+/[^/]+$ -/// Where the three last groups represent product/config_id/name -fn extract_product_and_id_from_path(path: &str) -> Option<(String, String)> { - let mut components = path - .strip_prefix("datadog/") - .map_or_else( - || path.strip_prefix("employee/"), - |rest| { - if !rest.starts_with(char::is_numeric) { - None - } else { - rest.trim_start_matches(char::is_numeric).strip_prefix("/") - } - }, - )? - .split("/"); - - let (product, config_id) = ( - components.next()?.to_string(), - components.next()?.to_string(), +fn apply_remove(file: &Arc, config: &Arc) { + use libdd_remote_config::file_change_tracker::FilePath; + let config_id = file.path().config_id.clone(); + crate::dd_debug!( + "RemoteConfigClient: removing APM_TRACING config {}", + config_id ); - // Remove the last name part - let _ = components.next()?; - // Check if there are any remaining components after product, config_id, name - if components.next().is_some() || product.is_empty() || config_id.is_empty() { - return None; - } - Some((product, config_id)) + config.clear_remote_sampling_rules(Some(config_id)); } #[cfg(test)] @@ -1047,178 +523,29 @@ mod tests { use super::*; use crate::core::configuration::SamplingRuleConfig; use pretty_assertions::assert_eq; - use proptest::prelude::*; - use test_case::test_case; + use std::sync::Mutex; fn build_config_for_handler() -> Arc { Arc::new(Config::builder().build()) } - #[test] - fn test_client_capabilities() { - let caps = ClientCapabilities::new(); - // Check that the encoded capabilities is a non-empty base64 string - let encoded = caps.encode(); - assert!(!encoded.is_empty()); - - // The encoded value should decode to contain our capability bit - use base64::Engine; - let decoded = base64::engine::general_purpose::STANDARD - .decode(&encoded) - .unwrap(); - - // Reconstruct the u64 from the variable-length big-endian bytes - let mut bytes = [0u8; 8]; - let offset = 8 - decoded.len(); - bytes[offset..].copy_from_slice(&decoded); - let value = u64::from_be_bytes(bytes); - - // Both APM_TRACING_SAMPLE_RATE (bit 12) and APM_TRACING_SAMPLE_RULES - // (bit 29) must be advertised; the backend uses each independently to - // decide which RC config types it will offer in the Datadog UI for - // this service. - let expected = ClientCapabilities::APM_TRACING_SAMPLE_RATE - | ClientCapabilities::APM_TRACING_SAMPLE_RULES; - assert_eq!(value, expected); - assert_eq!( - value & ClientCapabilities::APM_TRACING_SAMPLE_RATE, - ClientCapabilities::APM_TRACING_SAMPLE_RATE - ); - assert_eq!( - value & ClientCapabilities::APM_TRACING_SAMPLE_RULES, - ClientCapabilities::APM_TRACING_SAMPLE_RULES - ); + fn build_config_for_handler_with_target(service: &str, env: &str) -> Arc { + let mut builder = Config::builder(); + builder.set_service(service.to_string()); + builder.set_env(env.to_string()); + Arc::new(builder.build()) } - #[test] - fn test_request_serialization() { - // Test that our request format matches the expected structure - let state = ClientState { - root_version: 1, - targets_version: 122282776, - config_states: vec![], - has_error: false, - error: None, - backend_client_state: Some("test_backend_state".to_string()), - }; - - let client_info = ClientInfo { - state: Some(state), - id: "test-client-id".to_string(), - products: vec!["APM_TRACING".to_string()], - is_tracer: true, - client_tracer: Some(ClientTracer { - runtime_id: "test-runtime-id".to_string(), - language: "rust".to_string(), - tracer_version: "0.0.1".to_string(), - service: "test-service".to_string(), - extra_services: vec![], - env: Some("test-env".to_string()), - app_version: Some("1.0.0".to_string()), - tags: vec![], - }), - capabilities: ClientCapabilities::new().encode(), - }; - - let request = ConfigRequest { - client: client_info, - cached_target_files: Vec::new(), - }; - - // Serialize and verify the structure - let json = serde_json::to_value(&request).unwrap(); - - // Check top-level structure - assert!(json.get("client").is_some()); - // cached_target_files should be an empty array when empty - assert_eq!( - json.get("cached_target_files"), - Some(&serde_json::json!([])) - ); - - let client = &json["client"]; - - // Check client structure - assert_eq!(client["id"], "test-client-id"); - assert_eq!(client["products"], serde_json::json!(["APM_TRACING"])); - assert_eq!(client["is_tracer"], true); - - // Check client_tracer structure - let client_tracer = &client["client_tracer"]; - assert_eq!(client_tracer["runtime_id"], "test-runtime-id"); - assert_eq!(client_tracer["language"], "rust"); - assert_eq!(client_tracer["service"], "test-service"); - assert_eq!(client_tracer["extra_services"], serde_json::json!([])); - assert_eq!(client_tracer["env"], "test-env"); - assert_eq!(client_tracer["app_version"], "1.0.0"); - - // Check state structure - let state = &client["state"]; - assert_eq!(state["root_version"], 1); - assert_eq!(state["targets_version"], 122282776); - assert_eq!(state["has_error"], false); - assert_eq!(state["backend_client_state"], "test_backend_state"); - - // Check capabilities is a base64 encoded string - let capabilities = &client["capabilities"]; - assert!(capabilities.is_string()); - assert!(!capabilities.as_str().unwrap().is_empty()); + /// Parse a JSON payload with [`ApmTracingConfig::parse`] and apply it + /// through the same code path the worker uses. Used by handler tests so + /// they exercise the registry-facing parser and the application step + /// together. + fn apply_payload(payload: &[u8], config: &Arc) -> Result<()> { + let cfg = ApmTracingConfig::parse(payload).map_err(|e| anyhow::anyhow!("parse: {e}"))?; + apply_apm_tracing(&cfg, config) } - #[test] - fn test_request_serialization_with_error() { - // Test that error field is included when has_error is true - let state = ClientState { - root_version: 1, - targets_version: 1, - config_states: vec![], - has_error: true, - error: Some("Test error message".to_string()), - backend_client_state: None, - }; - - let client_info = ClientInfo { - state: Some(state), - id: "test-client-id".to_string(), - products: vec!["APM_TRACING".to_string()], - is_tracer: true, - client_tracer: Some(ClientTracer { - runtime_id: "test-runtime-id".to_string(), - language: "rust".to_string(), - tracer_version: "0.0.1".to_string(), - service: "test-service".to_string(), - extra_services: vec!["service1".to_string(), "service2".to_string()], - env: None, - app_version: None, - tags: vec![], - }), - capabilities: ClientCapabilities::new().encode(), - }; - - let request = ConfigRequest { - client: client_info, - cached_target_files: Vec::new(), - }; - - let json = serde_json::to_value(&request).unwrap(); - let state = &json["client"]["state"]; - - // Verify error field is present when has_error is true - assert_eq!(state["has_error"], true); - assert_eq!(state["error"], "Test error message"); - - // Verify extra_services is populated - let client_tracer = &json["client"]["client_tracer"]; - assert_eq!( - client_tracer["extra_services"], - serde_json::json!(["service1", "service2"]) - ); - - // Verify None values are not included in JSON - assert!(client_tracer.get("env").is_none()); - assert!(client_tracer.get("app_version").is_none()); - assert!(state.get("backend_client_state").is_none()); - } + // ── Parser tests (ApmTracingConfig / LibConfig) ────────────────────── #[test] fn test_apm_tracing_config_parsing() { @@ -1239,7 +566,6 @@ mod tests { assert!(config.lib_config.tracing_sampling_rules.is_some()); let rules_value = config.lib_config.tracing_sampling_rules.unwrap(); - // Parse the raw JSON value to verify the content let rules: Vec = serde_json::from_value(rules_value).unwrap(); assert_eq!(rules.len(), 1); assert_eq!(rules[0]["sample_rate"], 0.5); @@ -1249,7 +575,6 @@ mod tests { #[test] fn test_apm_tracing_config_full_schema() { - // Test parsing a more complete configuration let json = r#"{ "id": "42", "lib_config": { @@ -1275,24 +600,16 @@ mod tests { }"#; let config: ApmTracingConfig = serde_json::from_str(json).unwrap(); - assert!(config.lib_config.tracing_sampling_rules.is_some()); let rules_value = config.lib_config.tracing_sampling_rules.unwrap(); - - // Parse the raw JSON value to verify the content let rules: Vec = serde_json::from_value(rules_value).unwrap(); assert_eq!(rules.len(), 2); - - // Check first rule assert_eq!(rules[0]["sample_rate"], 0.3); assert_eq!(rules[0]["service"], "web-api"); assert_eq!(rules[0]["name"], "GET /users/*"); assert_eq!(rules[0]["resource"], "UserController.list"); - assert_eq!(rules[0]["tags"].as_object().unwrap().len(), 2); assert_eq!(rules[0]["tags"]["environment"], "production"); assert_eq!(rules[0]["tags"]["region"], "us-east-1"); assert_eq!(rules[0]["provenance"], "customer"); - - // Check second rule assert_eq!(rules[1]["sample_rate"], 1.0); assert_eq!(rules[1]["service"], "auth-service"); assert_eq!(rules[1]["provenance"], "dynamic"); @@ -1301,982 +618,14 @@ mod tests { #[test] fn test_apm_tracing_config_empty() { let json = r#"{}"#; - let config: LibConfig = serde_json::from_str(json).unwrap(); assert!(config.tracing_sampling_rules.is_none()); } - #[test] - fn test_cached_target_files() { - // Test that cached_target_files is properly serialized - let cached_file = CachedTargetFile { - path: "datadog/2/APM_TRACING/config123/config".to_string(), - length: 256, - hashes: vec![Hash { - algorithm: "sha256".to_string(), - hash: "abc123def456".to_string(), - }], - }; - - let request = ConfigRequest { - client: ClientInfo { - state: None, - id: "test-id".to_string(), - products: vec!["APM_TRACING".to_string()], - is_tracer: true, - client_tracer: None, - capabilities: ClientCapabilities::new().encode(), - }, - cached_target_files: vec![cached_file.clone()], - }; - - let json = serde_json::to_value(&request).unwrap(); - let cached = &json["cached_target_files"][0]; - - assert_eq!(cached["path"], "datadog/2/APM_TRACING/config123/config"); - assert_eq!(cached["length"], 256); - assert_eq!(cached["hashes"][0]["algorithm"], "sha256"); - assert_eq!(cached["hashes"][0]["hash"], "abc123def456"); - } - - #[test] - fn test_validate_signed_target_files() { - // Create a mock RemoteConfigClient for testing - let config = Arc::new(Config::builder().build()); - let client = RemoteConfigClient::new(config).unwrap(); - - // Test case 1: Target file exists in signed targets - let target_files = vec![TargetFile { - path: "datadog/2/APM_TRACING/config123/config".to_string(), - raw: "base64_encoded_content".to_string(), - }]; - - let signed_targets = serde_json::json!({ - "datadog/2/APM_TRACING/config123/config": { - "custom": {"id": "config123", "v": 1} - } - }); - - let client_configs = None; - - // Should pass validation - assert!(client - .validate_signed_target_files(&target_files, &Some(signed_targets), &client_configs) - .is_ok()); - - // Test case 2: Target file exists in client configs - let target_files = vec![TargetFile { - path: "datadog/2/APM_TRACING/config456/config".to_string(), - raw: "base64_encoded_content".to_string(), - }]; - - let signed_targets = None; - let client_configs = Some(vec!["datadog/2/APM_TRACING/config456/config".to_string()]); - - // Should pass validation - assert!(client - .validate_signed_target_files(&target_files, &signed_targets, &client_configs) - .is_ok()); - - // Test case 3: Target file exists in both signed targets and client configs - let target_files = vec![TargetFile { - path: "datadog/2/APM_TRACING/config789/config".to_string(), - raw: "base64_encoded_content".to_string(), - }]; - - let signed_targets = serde_json::json!({ - "datadog/2/APM_TRACING/config789/config": { - "custom": {"id": "config789", "v": 1} - } - }); - let client_configs = Some(vec!["datadog/2/APM_TRACING/config789/config".to_string()]); - - // Should pass validation - assert!(client - .validate_signed_target_files(&target_files, &Some(signed_targets), &client_configs) - .is_ok()); - - // Test case 4: Target file exists in neither signed targets nor client configs - let target_files = vec![TargetFile { - path: "datadog/2/APM_TRACING/invalid_config/config".to_string(), - raw: "base64_encoded_content".to_string(), - }]; - - let signed_targets = serde_json::json!({ - "datadog/2/APM_TRACING/other_config/config": { - "custom": {"id": "other_config", "v": 1} - } - }); - let client_configs = Some(vec![ - "datadog/2/APM_TRACING/another_config/config".to_string() - ]); - - // Should fail validation - let result = client.validate_signed_target_files( - &target_files, - &Some(signed_targets), - &client_configs, - ); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("target file datadog/2/APM_TRACING/invalid_config/config not exists in client_config and signed targets")); - - // Test case 5: Empty target files should pass validation - let target_files = vec![]; - let signed_targets = None; - let client_configs = None; - - // Should pass validation - assert!(client - .validate_signed_target_files(&target_files, &signed_targets, &client_configs) - .is_ok()); - } - - #[test] - fn test_parse_example_response() { - // Create a ConfigResponse object that represents the example response - let config_response = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogM319Cg==".to_string()), // base64 encoded targets with proper structure - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/apm-tracing-sampling/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(), // base64 encoded APM config - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/apm-tracing-sampling/config".to_string(), - ]), - }; - - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // For testing purposes, we'll verify the config was updated by checking the rules - - // Process the response - this should update the client's state and process APM_TRACING - // configs - let result = client.process_response(config_response); - assert!(result.is_ok(), "process_response should succeed"); - - // Verify that the client's state was updated correctly - let state = client.state.lock().unwrap(); - assert_eq!(state.targets_version, 3); - assert_eq!( - state.backend_client_state, - Some("eyJfooIOiAiYmFoIn0=".to_string()) - ); - assert!(!state.has_error); - - // Verify that APM_TRACING config states were added - assert_eq!(state.config_states.len(), 1); - let config_state = &state.config_states[0]; - assert_eq!(config_state.product, "APM_TRACING"); - assert_eq!(config_state.apply_state, 2); // success - - // Verify that APM_TRACING cached files were added - let cached_files = client.cached_target_files; - assert_eq!(cached_files.len(), 1); - assert_eq!( - cached_files[0].path, - "datadog/2/APM_TRACING/apm-tracing-sampling/config" - ); - // Cached file length is the decoded bytes length (not base64 string length) - assert_eq!(cached_files[0].length, 105); - assert_eq!(cached_files[0].hashes.len(), 1); - assert_eq!(cached_files[0].hashes[0].algorithm, "sha256"); - - // Verify that the config was updated with the processed rules - let config = client.config; - let rules = config.trace_sampling_rules(); - assert_eq!(rules.len(), 1); - assert_eq!(rules[0].sample_rate, 0.5); - assert_eq!(rules[0].service, Some("test-service".to_string())); - } - - #[test] - fn test_parse_multi_product_response() { - // This test verifies that our implementation correctly skips non-APM_TRACING - // configs and only processes APM_TRACING configs. The multi-product response contains - // ASM_FEATURES and LIVE_DEBUGGING configs which should be ignored. - - // Create a ConfigResponse object that represents a multi-product response - let config_response = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogMn19Cg==".to_string()), // base64 encoded targets with proper structure - target_files: Some(vec![ - TargetFile { - path: "datadog/2/ASM_FEATURES/ASM_FEATURES-base/config".to_string(), - raw: "eyJhc20tZmVhdHVyZXMiOiB7ImVuYWJsZWQiOiB0cnVlfX0=".to_string(), // base64 encoded config - }, - TargetFile { - path: "datadog/2/LIVE_DEBUGGING/LIVE_DEBUGGING-base/config".to_string(), - raw: "eyJsaXZlLWRlYnVnZ2luZyI6IHsiZW5hYmxlZCI6IGZhbHNlfX0=".to_string(), // base64 encoded config - }, - ]), - client_configs: Some(vec![ - "datadog/2/ASM_FEATURES/ASM_FEATURES-base/config".to_string(), - "datadog/2/LIVE_DEBUGGING/LIVE_DEBUGGING-base/config".to_string(), - ]), - }; - - // Create a RemoteConfigClient and process the response - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // Process the response - this should update the client's state - let result = client.process_response(config_response); - assert!(result.is_ok(), "process_response should succeed"); - - // Verify that the client's state was updated correctly - let state = client.state.lock().unwrap(); - assert_eq!(state.targets_version, 2); - assert_eq!( - state.backend_client_state, - Some("eyJfooIOiAiYmFoIn0=".to_string()) - ); - assert!(!state.has_error); - - // Verify that no config states were added since we don't process non-APM_TRACING products - assert_eq!(state.config_states.len(), 0); - - // Verify that cached target files were not added since they're not APM_TRACING - let cached_files = client.cached_target_files; - assert_eq!(cached_files.len(), 0); - } - - #[test] - fn test_config_update_from_remote() { - // Test that the config is updated when sampling rules are received - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // Process a config response with sampling rules - let config_response = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogM319Cg==".to_string()), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/test-config/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(), - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/test-config/config".to_string(), - ]), - }; - - let result = client.process_response(config_response); - assert!(result.is_ok(), "process_response should succeed"); - - // Verify that the config was updated with the sampling rules - let config = client.config; - let rules = config.trace_sampling_rules(); - assert_eq!(rules.len(), 1); - assert_eq!(rules[0].sample_rate, 0.5); - assert_eq!(rules[0].service, Some("test-service".to_string())); - } - - #[test] - fn test_tuf_targets_parsing() { - // Test parsing of a realistic TUF targets file structure - // Based on the example provided in the user query - let tuf_targets_json = r#"{ - "signatures": [ - { - "keyid": "5c4ece41241a1bb513f6e3e5df74ab7d5183dfffbd71bfd43127920d880569fd", - "sig": "4dd483db8b4aff81a9afd2ed4eaeb23fe3aca9a148a7a8942e24e8c5ef911e2692f94492b882727b257dacfbf6bcea09d6e26ea28ac145fcb4254ea046be3b03" - } - ], - "signed": { - "_type": "targets", - "custom": { - "opaque_backend_state": "eyJ2ZXJzaW9uIjoxLCJzdGF0ZSI6eyJmaWxlX2hhc2hlcyI6WyJGZXJOT1FyMStmTThKWk9TY0crZllucnhXMWpKN0w0ZlB5aGtxUWVCT3dJPSIsInd1aW9BVm1Qcy9oNEpXMDh1dnI1bi9meERLQ3lKdG1sQmRjaDNOcFdLZDg9IiwiOGFDYVJFc3hIV3R3SFNFWm5SV0pJYmtENXVBNUtETENoZG8vZ0RNdnJJMD0iXX19" - }, - "expires": "2022-09-22T09:01:04Z", - "spec_version": "1.0.0", - "targets": { - "datadog/2/APM_SAMPLING/dynamic_rates/config": { - "custom": { - "v": 27423 - }, - "hashes": { - "sha256": "c2e8a801598fb3f878256d3cbafaf99ff7f10ca0b226d9a505d721dcda5629df" - }, - "length": 58409 - }, - "employee/ASM_DD/1.recommended.json/config": { - "custom": { - "v": 1 - }, - "hashes": { - "sha256": "15eacd390af5f9f33c259392706f9f627af15b58c9ecbe1f3f2864a907813b02" - }, - "length": 235228 - }, - "employee/CWS_DD/4.default.policy/config": { - "custom": { - "v": 1 - }, - "hashes": { - "sha256": "f1a09a444b311d6b701d21199d158921b903e6e0392832c285da3f80332fac8d" - }, - "length": 34777 - } - }, - "version": 23755701 - } -}"#; - - // Parse the TUF targets structure - let targets: SignedTargets = serde_json::from_str(tuf_targets_json) - .expect("Should successfully parse TUF targets JSON"); - - // Verify signatures array is parsed correctly - assert!(targets.signatures.is_some()); - let signatures = targets.signatures.unwrap(); - assert_eq!(signatures.len(), 1); - - // Verify the signed targets structure - assert_eq!(targets.signed.target_type, "targets"); - assert_eq!(targets.signed.expires, "2022-09-22T09:01:04Z"); - assert_eq!(targets.signed.spec_version, "1.0.0"); - assert_eq!(targets.signed.version, 23755701); - - // Verify custom metadata with opaque_backend_state - assert!(targets.signed.custom.is_some()); - let custom = targets.signed.custom.unwrap(); - let backend_state = custom - .get("opaque_backend_state") - .and_then(|v| v.as_str()) - .expect("Should have opaque_backend_state"); - assert_eq!(backend_state, "eyJ2ZXJzaW9uIjoxLCJzdGF0ZSI6eyJmaWxlX2hhc2hlcyI6WyJGZXJOT1FyMStmTThKWk9TY0crZllucnhXMWpKN0w0ZlB5aGtxUWVCT3dJPSIsInd1aW9BVm1Qcy9oNEpXMDh1dnI1bi9meERLQ3lKdG1sQmRjaDNOcFdLZDg9IiwiOGFDYVJFc3hIV3R3SFNFWm5SV0pJYmtENXVBNUtETENoZG8vZ0RNdnJJMD0iXX19"); - - // Verify targets parsing - assert_eq!(targets.signed.targets.len(), 3); - - // Test APM_SAMPLING target - let apm_sampling = targets - .signed - .targets - .get("datadog/2/APM_SAMPLING/dynamic_rates/config") - .expect("Should have APM_SAMPLING target"); - assert_eq!(apm_sampling.length, 58409); - assert_eq!( - apm_sampling.hashes.get("sha256").unwrap(), - "c2e8a801598fb3f878256d3cbafaf99ff7f10ca0b226d9a505d721dcda5629df" - ); - let apm_custom = apm_sampling.custom.as_ref().unwrap(); - assert_eq!(apm_custom.get("v").unwrap().as_u64().unwrap(), 27423); - - // Test ASM_DD target - let asm_dd = targets - .signed - .targets - .get("employee/ASM_DD/1.recommended.json/config") - .expect("Should have ASM_DD target"); - assert_eq!(asm_dd.length, 235228); - assert_eq!( - asm_dd.hashes.get("sha256").unwrap(), - "15eacd390af5f9f33c259392706f9f627af15b58c9ecbe1f3f2864a907813b02" - ); - let asm_custom = asm_dd.custom.as_ref().unwrap(); - assert_eq!(asm_custom.get("v").unwrap().as_u64().unwrap(), 1); - - // Test CWS_DD target - let cws_dd = targets - .signed - .targets - .get("employee/CWS_DD/4.default.policy/config") - .expect("Should have CWS_DD target"); - assert_eq!(cws_dd.length, 34777); - assert_eq!( - cws_dd.hashes.get("sha256").unwrap(), - "f1a09a444b311d6b701d21199d158921b903e6e0392832c285da3f80332fac8d" - ); - let cws_custom = cws_dd.custom.as_ref().unwrap(); - assert_eq!(cws_custom.get("v").unwrap().as_u64().unwrap(), 1); - } - - // ===== Valid Path Tests ===== - - #[test_case("datadog/2/APM_TRACING/config123/config", "APM_TRACING", "config123")] - #[test_case( - "datadog/2/LIVE_DEBUGGING/LIVE_DEBUGGING-base/config", - "LIVE_DEBUGGING", - "LIVE_DEBUGGING-base" - )] - #[test_case( - "datadog/2/AGENT_CONFIG/dynamic_rates/config", - "AGENT_CONFIG", - "dynamic_rates" - )] - #[test_case( - "datadog/2/ASM_FEATURES/ASM_FEATURES-base/config", - "ASM_FEATURES", - "ASM_FEATURES-base" - )] - #[test_case( - "datadog/2/APM_SAMPLING/dynamic_rates/config", - "APM_SAMPLING", - "dynamic_rates" - )] - fn test_valid_datadog_paths(path: &str, expected_product: &str, expected_id: &str) { - let result = extract_product_and_id_from_path(path); - assert_eq!( - result, - Some((expected_product.to_string(), expected_id.to_string())) - ); - } - - #[test_case( - "employee/ASM_DD/1.recommended.json/config", - "ASM_DD", - "1.recommended.json" - )] - #[test_case( - "employee/CWS_DD/4.default.policy/config", - "CWS_DD", - "4.default.policy" - )] - #[test_case("employee/TEST_PRODUCT/test-id/some-name", "TEST_PRODUCT", "test-id")] - fn test_valid_employee_paths(path: &str, expected_product: &str, expected_id: &str) { - let result = extract_product_and_id_from_path(path); - assert_eq!( - result, - Some((expected_product.to_string(), expected_id.to_string())) - ); - } - - #[test_case("datadog/0/PRODUCT/id/name", "PRODUCT", "id")] - #[test_case("datadog/1/PRODUCT/id/name", "PRODUCT", "id")] - #[test_case("datadog/2/PRODUCT/id/name", "PRODUCT", "id")] - #[test_case("datadog/99/PRODUCT/id/name", "PRODUCT", "id")] - #[test_case("datadog/123/PRODUCT/id/name", "PRODUCT", "id")] - #[test_case("datadog/999999/PRODUCT/id/name", "PRODUCT", "id")] - fn test_various_numeric_versions(path: &str, expected_product: &str, expected_id: &str) { - let result = extract_product_and_id_from_path(path); - assert_eq!( - result, - Some((expected_product.to_string(), expected_id.to_string())) - ); - } - - #[test_case("datadog/2/PRODUCT-NAME/config-id-123/file.json", "PRODUCT-NAME", "config-id-123" ; "hyphens")] - #[test_case("datadog/2/PRODUCT_NAME/config_id_123/file_name", "PRODUCT_NAME", "config_id_123" ; "underscores")] - #[test_case("datadog/2/PRODUCT.NAME/config.id.123/file.name", "PRODUCT.NAME", "config.id.123" ; "dots")] - #[test_case("employee/PR0D-UCT_123/id-with.chars/name", "PR0D-UCT_123", "id-with.chars" ; "mixed special chars")] - fn test_special_characters_in_components( - path: &str, - expected_product: &str, - expected_id: &str, - ) { - let result = extract_product_and_id_from_path(path); - assert_eq!( - result, - Some((expected_product.to_string(), expected_id.to_string())) - ); - } - - // ===== Invalid Path Tests ===== - - #[test_case("" ; "empty string")] - #[test_case(" " ; "single space")] - #[test_case(" " ; "multiple spaces")] - fn test_empty_and_whitespace(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test_case("invalid/path" ; "invalid prefix")] - #[test_case("invalid/2/PRODUCT/id/name" ; "invalid prefix with components")] - #[test_case("datadogs/2/PRODUCT/id/name" ; "typo in datadog")] - #[test_case("employe/PRODUCT/id/name" ; "typo in employee")] - #[test_case("PRODUCT/id/name" ; "missing prefix entirely")] - #[test_case("2/PRODUCT/id/name" ; "numeric prefix only")] - fn test_missing_prefix(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test_case("datadog/2" ; "datadog only version")] - #[test_case("datadog/2/PRODUCT" ; "datadog missing id and name")] - #[test_case("datadog/2/PRODUCT/config" ; "datadog missing name")] - #[test_case("employee/PRODUCT" ; "employee missing id and name")] - #[test_case("employee/PRODUCT/id" ; "employee missing name")] - fn test_insufficient_components(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test_case("datadog/2/PRODUCT/id/name/extra" ; "datadog one extra")] - #[test_case("datadog/2/PRODUCT/id/name/extra/more" ; "datadog two extra")] - #[test_case("employee/PRODUCT/id/name/extra" ; "employee one extra")] - #[test_case("employee/PRODUCT/id/name/extra/and/more" ; "employee three extra")] - fn test_too_many_components(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test_case("datadog/2/PROD/UCT/id/name" ; "slash in product")] - #[test_case("datadog/2/PRODUCT/conf/ig/name" ; "slash in config_id")] - #[test_case("datadog/2/PRODUCT/id/na/me" ; "slash in name")] - fn test_slashes_in_components(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test_case("/datadog/2/PRODUCT/id/name" ; "leading slash datadog")] - #[test_case("datadog/2/PRODUCT/id/name/" ; "trailing slash datadog")] - #[test_case("/employee/PRODUCT/id/name" ; "leading slash employee")] - fn test_leading_trailing_slashes(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - // ===== Property-Based Tests ===== - - proptest! { - #[test] - fn test_valid_datadog_paths_property( - version in 0u32..1000000, - product in "[A-Z_]{1,20}", - config_id in "[a-zA-Z0-9_-]{1,30}", - name in "[a-zA-Z0-9_.-]{1,30}" - ) { - let path = format!("datadog/{}/{}/{}/{}", version, product, config_id, name); - let result = extract_product_and_id_from_path(&path); - - prop_assert_eq!( - result, - Some((product.clone(), config_id.clone())), - "Valid datadog path should parse successfully: {}", - path - ); - } - - #[test] - fn test_valid_employee_paths_property( - product in "[A-Z_]{1,20}", - config_id in "[a-zA-Z0-9_.-]{1,30}", - name in "[a-zA-Z0-9_.-]{1,30}" - ) { - let path = format!("employee/{}/{}/{}", product, config_id, name); - let result = extract_product_and_id_from_path(&path); - - prop_assert_eq!( - result, - Some((product.clone(), config_id.clone())), - "Valid employee path should parse successfully: {}", - path - ); - } - - #[test] - fn test_invalid_prefix_property( - prefix in "[a-z]{1,20}", - rest in "[a-zA-Z0-9/_-]{1,50}" - ) { - prop_assume!(prefix != "datadog" && prefix != "employee"); - let path = format!("{}/{}", prefix, rest); - let result = extract_product_and_id_from_path(&path); - - prop_assert_eq!( - result, - None, - "Path with invalid prefix should fail: {}", - path - ); - } - - #[test] - fn test_too_few_components_property( - version in 0u32..100, - component_count in 0usize..3 - ) { - let mut components = vec![format!("datadog/{}", version)]; - for i in 0..component_count { - components.push(format!("comp{}", i)); - } - let path = components.join("/"); - let result = extract_product_and_id_from_path(&path); - - prop_assert_eq!( - result, - None, - "Path with {} components should fail: {}", - component_count, - path - ); - } - - #[test] - fn test_too_many_components_property( - version in 0u32..100, - product in "[A-Z_]{1,20}", - config_id in "[a-zA-Z0-9_-]{1,30}", - name in "[a-zA-Z0-9_.-]{1,30}", - extra_count in 1usize..5 - ) { - let mut path = format!("datadog/{}/{}/{}/{}", version, product, config_id, name); - for i in 0..extra_count { - path.push_str(&format!("/extra{}", i)); - } - let result = extract_product_and_id_from_path(&path); - - prop_assert_eq!( - result, - None, - "Path with {} extra components should fail: {}", - extra_count, - path - ); - } - } - - // ===== Regression Tests for Real-World Paths ===== - - #[test_case( - "datadog/2/APM_SAMPLING/dynamic_rates/config", - "APM_SAMPLING", - "dynamic_rates" - )] - #[test_case( - "employee/ASM_DD/1.recommended.json/config", - "ASM_DD", - "1.recommended.json" - )] - #[test_case( - "employee/CWS_DD/4.default.policy/config", - "CWS_DD", - "4.default.policy" - )] - #[test_case( - "datadog/2/APM_TRACING/apm-tracing-sampling/config", - "APM_TRACING", - "apm-tracing-sampling" - )] - #[test_case( - "datadog/2/ASM_FEATURES/ASM_FEATURES-base/config", - "ASM_FEATURES", - "ASM_FEATURES-base" - )] - #[test_case( - "datadog/2/LIVE_DEBUGGING/LIVE_DEBUGGING-base/config", - "LIVE_DEBUGGING", - "LIVE_DEBUGGING-base" - )] - fn test_real_world_examples(path: &str, expected_product: &str, expected_id: &str) { - let result = extract_product_and_id_from_path(path); - assert_eq!( - result, - Some((expected_product.to_string(), expected_id.to_string())) - ); - } - - // ===== Edge Cases ===== - - #[test_case("datadog/2/PRODUCT/id-\u{00E9}/name" ; "unicode e with acute")] - #[test_case("employee/PRODUCT/id-\u{4E2D}/name" ; "unicode chinese character")] - fn test_unicode_in_components(path: &str) { - // These should parse successfully since unicode chars are valid in [^/]+ - let result = extract_product_and_id_from_path(path); - assert!(result.is_some()); - } - - #[test_case("DATADOG/2/PRODUCT/id/name" ; "uppercase DATADOG")] - #[test_case("Datadog/2/PRODUCT/id/name" ; "capitalized Datadog")] - #[test_case("EMPLOYEE/PRODUCT/id/name" ; "uppercase EMPLOYEE")] - #[test_case("Employee/PRODUCT/id/name" ; "capitalized Employee")] - fn test_case_sensitivity(path: &str) { - assert_eq!(extract_product_and_id_from_path(path), None); - } - - #[test] - fn test_product_registry() { - let registry = ProductRegistry::new(); - - // Should have APM_TRACING handler registered - assert!(registry.get_handler("APM_TRACING").is_some()); - - // Should not have unknown products - assert!(registry.get_handler("UNKNOWN_PRODUCT").is_none()); - } - - #[test] - fn test_apm_tracing_handler() { - let handler = ApmTracingHandler; - assert_eq!(handler.product_name(), "APM_TRACING"); - - // Test processing config - this should not panic for valid JSON - let config = Arc::new(Config::builder().build()); - let config_json = r#"{"id": "42", "lib_config": {"tracing_sampling_rules": [{"sample_rate": 0.5, "service": "test"}]}}"#; - - // This should succeed - let result = handler.process_config(config_json.as_bytes(), &config); - assert!(result.is_ok()); - - // Test invalid JSON - let invalid_json = "invalid json"; - let result = handler.process_config(invalid_json.as_bytes(), &config); - assert!(result.is_err()); - } - - #[test] - fn test_config_states_cleared_between_processing_cycles() { - // Test that config_states are cleared before adding new ones to prevent memory leak - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // First processing cycle - add one config - let config_response_1 = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogMX19Cg==".to_string()), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/config1/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0xIn1dfX0=".to_string(), - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/config1/config".to_string(), - ]), - }; - - // Process first response - let result = client.process_response(config_response_1); - assert!(result.is_ok(), "First process_response should succeed"); - - // Verify first config state was added - { - let state = client.state.lock().unwrap(); - assert_eq!(state.config_states.len(), 1); - assert_eq!(state.config_states[0].id, "config1"); - assert_eq!(state.config_states[0].apply_state, 2); // success - } - - // Second processing cycle - add different configs - let config_response_2 = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogMn19Cg==".to_string()), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/config2/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJpZCI6IjQyIiwgInRyYWNpbmdfc2FtcGxpbmdfcnVsZXMiOiBbeyJzYW1wbGVfcmF0ZSI6IDAuNzUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0yIn1dfX0=".to_string(), - }, - TargetFile { - path: "datadog/2/APM_TRACING/config3/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJpZCI6IjQyIiwgInRyYWNpbmdfc2FtcGxpbmdfcnVsZXMiOiBbeyJzYW1wbGVfcmF0ZSI6IDAuMjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0yIn1dfX0=".to_string(), - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/config2/config".to_string(), - "datadog/2/APM_TRACING/config3/config".to_string(), - ]), - }; - - // Process second response - let result = client.process_response(config_response_2); - assert!(result.is_ok(), "Second process_response should succeed"); - - // Verify config_states were cleared and only contains the new configs - { - let state = client.state.lock().unwrap(); - // Should have exactly 2 configs (config2 and config3), not 3 (which would include - // config1) - assert_eq!(state.config_states.len(), 2); - - // Check that we only have the new config IDs, not the old one - let config_ids: Vec = - state.config_states.iter().map(|cs| cs.id.clone()).collect(); - assert!(config_ids.contains(&"config2".to_string())); - assert!(config_ids.contains(&"config3".to_string())); - assert!(!config_ids.contains(&"config1".to_string())); // Should not contain old config - - // All should be successful - for config_state in &state.config_states { - assert_eq!(config_state.apply_state, 2); // success - assert_eq!(config_state.product, "APM_TRACING"); - } - } - - // Third processing cycle - empty target files - let config_response_3 = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogM319Cg==".to_string()), - target_files: Some(vec![]), // Empty target files - client_configs: Some(vec![]), - }; - - // Process third response - let result = client.process_response(config_response_3); - assert!(result.is_ok(), "Third process_response should succeed"); - - // Verify config_states remain unchanged when no configs are processed - // (since clearing only happens when we're about to add new config states) - { - let state = client.state.lock().unwrap(); - assert_eq!(state.config_states.len(), 2); // Should still have config2 and config3 - - let config_ids: Vec = - state.config_states.iter().map(|cs| cs.id.clone()).collect(); - assert!(config_ids.contains(&"config2".to_string())); - assert!(config_ids.contains(&"config3".to_string())); - } - } - - #[test] - fn test_config_states_cleared_on_error_configs() { - // Test that config_states are cleared even when processing results in errors - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // First processing cycle - add successful config - let config_response_1 = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogMX19Cg==".to_string()), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/good_config/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(), - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/good_config/config".to_string(), - ]), - }; - - // Process first response - let result = client.process_response(config_response_1); - assert!(result.is_ok(), "First process_response should succeed"); - - // Verify first config state was added - { - let state = client.state.lock().unwrap(); - assert_eq!(state.config_states.len(), 1); - assert_eq!(state.config_states[0].id, "good_config"); - assert_eq!(state.config_states[0].apply_state, 2); // success - } - - // Second processing cycle - add config with invalid JSON (will cause error) - let config_response_2 = ConfigResponse { - roots: None, - targets: Some("eyJzaWduZWQiOiB7Il90eXBlIjogInRhcmdldHMiLCAiY3VzdG9tIjogeyJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUpmb29JT2lBaVltRm9JbjA9In0sICJleHBpcmVzIjogIjIwMjQtMTItMzFUMjM6NTk6NTlaIiwgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsICJ0YXJnZXRzIjoge30sICJ2ZXJzaW9uIjogMn19Cg==".to_string()), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/bad_config/config".to_string(), - raw: "aW52YWxpZCBqc29u".to_string(), // "invalid json" in base64 - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/bad_config/config".to_string(), - ]), - }; - - // Process second response - let result = client.process_response(config_response_2); - assert!( - result.is_ok(), - "Second process_response should succeed (even with config errors)" - ); - - // Verify config_states were cleared and only contains the new error config - { - let state = client.state.lock().unwrap(); - assert_eq!(state.config_states.len(), 1); // Should have only the error config - assert_eq!(state.config_states[0].id, "bad_config"); - assert_eq!(state.config_states[0].apply_state, 3); // error - assert!(state.config_states[0].apply_error.is_some()); - - // Should not contain the previous successful config - assert_ne!(state.config_states[0].id, "good_config"); - } - } - - #[test] - fn test_tuf_targets_integration_with_remote_config() { - // Test that we can process a TUF targets response through the remote config system - let config = Arc::new(Config::builder().build()); - let mut client = RemoteConfigClient::new(config).unwrap(); - - // Create a realistic TUF targets JSON and base64 encode it - let tuf_targets_json = r#"{ - "signatures": [ - { - "keyid": "5c4ece41241a1bb513f6e3e5df74ab7d5183dfffbd71bfd43127920d880569fd", - "sig": "4dd483db8b4aff81a9afd2ed4eaeb23fe3aca9a148a7a8942e24e8c5ef911e2692f94492b882727b257dacfbf6bcea09d6e26ea28ac145fcb4254ea046be3b03" - } - ], - "signed": { - "_type": "targets", - "custom": { - "opaque_backend_state": "eyJ2ZXJzaW9uIjoxLCJzdGF0ZSI6eyJmaWxlX2hhc2hlcyI6WyJGZXJOT1FyMStmTThKWk9TY0crZllucnhXMWpKN0w0ZlB5aGtxUWVCT3dJPSIsInd1aW9BVm1Qcy9oNEpXMDh1dnI1bi9meERLQ3lKdG1sQmRjaDNOcFdLZDg9IiwiOGFDYVJFc3hIV3R3SFNFWm5SV0pJYmtENXVBNUtETENoZG8vZ0RNdnJJMD0iXX19" - }, - "expires": "2022-09-22T09:01:04Z", - "spec_version": "1.0.0", - "targets": { - "datadog/2/APM_TRACING/test-sampling/config": { - "custom": { - "v": 100 - }, - "hashes": { - "sha256": "c2e8a801598fb3f878256d3cbafaf99ff7f10ca0b226d9a505d721dcda5629df" - }, - "length": 58409 - } - }, - "version": 23755701 - } -}"#; - - use base64::Engine; - let encoded_targets = - base64::engine::general_purpose::STANDARD.encode(tuf_targets_json.as_bytes()); - - // Create a config response with the TUF targets and a corresponding target file - let config_response = ConfigResponse { - roots: None, - targets: Some(encoded_targets), - target_files: Some(vec![ - TargetFile { - path: "datadog/2/APM_TRACING/test-sampling/config".to_string(), - raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjc1LCAic2VydmljZSI6ICJ0ZXN0LWFwcC1zZXJ2aWNlIn1dfX0=".to_string(), // base64 encoded sampling rules - }, - ]), - client_configs: Some(vec![ - "datadog/2/APM_TRACING/test-sampling/config".to_string() - ]), - }; - - // Process the response - let result = client.process_response(config_response); - assert!( - result.is_ok(), - "process_response should succeed: {result:?}" - ); - - // Verify state was updated with targets metadata - let state = client.state.lock().unwrap(); - assert_eq!(state.targets_version, 23755701); - assert_eq!( - state.backend_client_state, - Some("eyJ2ZXJzaW9uIjoxLCJzdGF0ZSI6eyJmaWxlX2hhc2hlcyI6WyJGZXJOT1FyMStmTThKWk9TY0crZllucnhXMWpKN0w0ZlB5aGtxUWVCT3dJPSIsInd1aW9BVm1Qcy9oNEpXMDh1dnI1bi9meERLQ3lKdG1sQmRjaDNOcFdLZDg9IiwiOGFDYVJFc3hIV3R3SFNFWm5SV0pJYmtENXVBNUtETENoZG8vZ0RNdnJJMD0iXX19".to_string()) - ); - - // Verify config states were updated with version from targets custom.v - assert_eq!(state.config_states.len(), 1); - let config_state = &state.config_states[0]; - assert!(config_state.id == "test-sampling" || config_state.id == "apm-tracing-sampling"); - assert_eq!(config_state.version, 100); // From custom.v in targets - assert_eq!(config_state.product, "APM_TRACING"); - - // Verify that the sampling rules were applied to the config - let config = client.config; - let rules = config.trace_sampling_rules(); - assert_eq!(rules.len(), 1); - assert_eq!(rules[0].sample_rate, 0.75); - assert_eq!(rules[0].service, Some("test-app-service".to_string())); - } - #[test] fn test_deserialize_tracing_sampling_rules_null() { let config_json = r#"{"id": "42", "lib_config": {"tracing_sampling_rules": null}}"#; - let tracing_config: ApmTracingConfig = - serde_json::from_str(config_json).expect("Json should be parsed"); - + let tracing_config: ApmTracingConfig = serde_json::from_str(config_json).unwrap(); assert!(tracing_config.lib_config.tracing_sampling_rules.is_some()); assert!(tracing_config .lib_config @@ -2288,9 +637,7 @@ mod tests { #[test] fn test_deserialize_tracing_sampling_rules_missing() { let config_json = r#"{"id": "42", "lib_config": {}}"#; - let tracing_config: ApmTracingConfig = - serde_json::from_str(config_json).expect("Json should be parsed"); - + let tracing_config: ApmTracingConfig = serde_json::from_str(config_json).unwrap(); assert!(tracing_config.lib_config.tracing_sampling_rules.is_none()); } @@ -2316,12 +663,41 @@ mod tests { #[test] fn test_deserialize_tracing_sampling_rate_missing() { - // Field absent entirely. let config_json = r#"{"id": "42", "lib_config": {}}"#; let tracing_config: ApmTracingConfig = serde_json::from_str(config_json).unwrap(); assert!(tracing_config.lib_config.tracing_sampling_rate.is_none()); } + // ── Registry round-trip ────────────────────────────────────────────── + + #[test] + fn test_registry_round_trip_apm_tracing() { + // The registry-driven parse path (used by ParsedFileStorage) must + // yield the same fields as the direct serde_json::from_slice path + // that today's apply_apm_tracing reads. + let registry = ParserRegistry::new() + .with::() + .expect("registry registration"); + let payload = br#"{ + "id": "rc-roundtrip", + "lib_config": {"tracing_sampling_rate": 0.25} + }"#; + let parsed = registry + .parse(RemoteConfigProduct::ApmTracing, payload) + .expect("registry parse") + .expect("ApmTracing parser registered"); + let cfg = parsed + .downcast::() + .expect("downcast to ApmTracingConfig"); + assert_eq!(cfg.id, "rc-roundtrip"); + assert_eq!( + cfg.lib_config.tracing_sampling_rate, + Some(serde_json::json!(0.25)) + ); + } + + // ── apply_apm_tracing tests (formerly handler tests) ───────────────── + #[test] fn test_handler_applies_only_rate_as_wildcard_rule() { // RC sends only tracing_sampling_rate -> handler installs a single @@ -2331,7 +707,7 @@ mod tests { "id": "rc-rate-only", "lib_config": {"tracing_sampling_rate": 0.25} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!( rules.len(), @@ -2365,11 +741,9 @@ mod tests { "id": "rc-rate-only-provenance", "lib_config": {"tracing_sampling_rate": 0.25} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let got = received.lock().unwrap(); - // The callback receives the full composed chain. The catch-all is the - // last entry; it must carry the libdd default provenance ("default"). let catch_all = got.last().expect("expected at least one rule"); assert_eq!(catch_all.sample_rate, 0.25); assert_eq!( @@ -2380,7 +754,6 @@ mod tests { #[test] fn test_handler_appends_rate_after_rules() { - // RC sends both rules and a rate -> rate becomes the last (wildcard) rule. let config = build_config_for_handler(); let payload = br#"{ "id": "rc-both", @@ -2391,7 +764,7 @@ mod tests { ] } }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 2); assert_eq!(rules[0].sample_rate, 0.9); @@ -2403,16 +776,14 @@ mod tests { #[test] fn test_handler_null_fields_clear_prior_override() { - // 1. Install rules via RC. let config = build_config_for_handler(); let install = br#"{ "id": "rc-install", "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(install, &config).unwrap(); + apply_payload(install, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 1); - // 2. Send explicit null for both fields -> override cleared. let clear = br#"{ "id": "rc-clear", "lib_config": { @@ -2420,38 +791,30 @@ mod tests { "tracing_sampling_rules": null } }"#; - ApmTracingHandler.process_config(clear, &config).unwrap(); - // After clearing, trace_sampling_rules() returns the local-config default - // (empty unless DD_TRACE_SAMPLING_RULES is set in the test environment). + apply_payload(clear, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 0); } #[test] fn test_handler_null_rate_only_clears_prior_override() { - // Explicit null on tracing_sampling_rate (with tracing_sampling_rules absent) - // must clear a prior remote override. let config = build_config_for_handler(); let install = br#"{ "id": "rc-install", "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(install, &config).unwrap(); + apply_payload(install, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 1); let clear = br#"{ "id": "rc-clear", "lib_config": {"tracing_sampling_rate": null} }"#; - ApmTracingHandler.process_config(clear, &config).unwrap(); + apply_payload(clear, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 0); } #[test] fn test_handler_rc_rules_with_list_tags_applied() { - // RC sends tags as a list-of-objects ([{key, value_glob}]); libdd-sampling - // (>=2.1.0) parses that wire shape natively, so no in-tracer normalization - // is needed. Regression guard: a list-shape tagged rule must apply with its - // tags preserved as a map. let config = build_config_for_handler(); let payload = br#"{ "id": "rc-list-tags", @@ -2468,7 +831,7 @@ mod tests { ] } }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 1); assert_eq!(rules[0].sample_rate, 0.5); @@ -2482,20 +845,14 @@ mod tests { #[test] fn test_handler_malformed_tags_rejects_update() { - // Bug B fail-closed guard: a sampling rule with malformed list-shape - // tags must not be installed in a broadened form. With the prior - // override of sample_rate=0.5 in place, sending a rule with one bad - // tag entry must leave the prior override intact. let config = build_config_for_handler(); - // 1. Install a working override. let install = br#"{ "id": "rc-install", "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(install, &config).unwrap(); + apply_payload(install, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 1); - // 2. Send a rule with a malformed tag entry. let bad = br#"{ "id": "rc-bad-tags", "lib_config": { @@ -2511,10 +868,7 @@ mod tests { ] } }"#; - // The libdatadog parse rejects list-shape tags, so process_config - // returns Err (post-Codex HIGH fix). The key invariant is that the - // prior remote override is not overwritten or cleared. - let result = ApmTracingHandler.process_config(bad, &config); + let result = apply_payload(bad, &config); assert!(result.is_err(), "malformed tags must propagate as Err"); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 1, "prior override must remain installed"); @@ -2523,11 +877,6 @@ mod tests { #[test] fn test_handler_malformed_tags_returns_error_to_dispatcher() { - // After the fix for the Codex HIGH finding: when libdatadog rejects the - // composed JSON (e.g., because the synthetic catch-all rule's tags were - // left in list-shape due to a malformed entry), process_config returns - // Err so the RC dispatcher records apply_state=3 and the bad target is - // not cached. let config = build_config_for_handler(); let payload = br#"{ "id": "rc-bad-tags", @@ -2544,7 +893,7 @@ mod tests { ] } }"#; - let result = ApmTracingHandler.process_config(payload, &config); + let result = apply_payload(payload, &config); assert!(result.is_err(), "malformed tags must propagate as Err"); } @@ -2555,8 +904,7 @@ mod tests { "id": "rc-neg-rate", "lib_config": {"tracing_sampling_rate": -0.1} }"#; - let result = ApmTracingHandler.process_config(payload, &config); - assert!(result.is_err(), "negative rate must be rejected"); + assert!(apply_payload(payload, &config).is_err()); } #[test] @@ -2566,28 +914,24 @@ mod tests { "id": "rc-high-rate", "lib_config": {"tracing_sampling_rate": 1.5} }"#; - let result = ApmTracingHandler.process_config(payload, &config); - assert!(result.is_err(), "rate > 1.0 must be rejected"); + assert!(apply_payload(payload, &config).is_err()); } #[test] fn test_handler_non_numeric_rate_rejects_update() { - // A schema-drifted rate (e.g. string) must be rejected as a malformed - // payload, not silently treated as a clear that would wipe an active - // remote override. let config = build_config_for_handler(); let install = br#"{ "id": "rc-install", "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(install, &config).unwrap(); + apply_payload(install, &config).unwrap(); assert_eq!(config.trace_sampling_rules().len(), 1); let bad = br#"{ "id": "rc-bad-rate", "lib_config": {"tracing_sampling_rate": "0.5"} }"#; - let result = ApmTracingHandler.process_config(bad, &config); + let result = apply_payload(bad, &config); assert!(result.is_err(), "non-numeric rate must be rejected"); // Prior override survives. assert_eq!(config.trace_sampling_rules().len(), 1); @@ -2596,9 +940,6 @@ mod tests { #[test] fn test_handler_rate_only_preserves_env_rules() { - // When RC delivers only tracing_sampling_rate (no rules), env-configured - // sampling rules must still apply for matching spans. Composed chain: - // [env_rules..., catch_all(rc_rate)]. let env_rule = SamplingRuleConfig { sample_rate: 0.55, name: Some("env_name".to_string()), @@ -2614,7 +955,7 @@ mod tests { "id": "rc-rate-only-with-env-rules", "lib_config": {"tracing_sampling_rate": 0.70} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 2, "expected env rule + synthetic catch-all"); @@ -2629,8 +970,6 @@ mod tests { #[test] fn test_handler_rc_rules_replace_env_rules() { - // Contract: when RC delivers tracing_sampling_rules (with or without a - // rate), env rules are fully replaced. RC rules + catch-all(rc_rate). let env_rule = SamplingRuleConfig { sample_rate: 0.55, name: Some("env_name".to_string()), @@ -2651,7 +990,7 @@ mod tests { ] } }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!( @@ -2668,9 +1007,6 @@ mod tests { #[test] fn test_handler_rc_rules_only_falls_back_to_env_rate_catch_all() { - // RC delivers rules without a rate; DD_TRACE_SAMPLE_RATE is set. The - // composed chain should be [rc_rules..., catch_all(env_rate)] so - // unmatched spans still get sampled at env_rate. let mut builder = Config::builder(); builder.set_trace_sample_rate(0.1); let config = Arc::new(builder.build()); @@ -2683,7 +1019,7 @@ mod tests { ] } }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 2, "expected rc rule + env-rate catch-all"); @@ -2695,9 +1031,6 @@ mod tests { #[test] fn test_handler_empty_rc_rules_array_preserves_env_rules() { - // Contract: an explicit empty `tracing_sampling_rules: []` is treated as - // "RC has no rules" — env rules survive. Operators clear RC rules by - // sending `tracing_sampling_rules: null`. This test locks that behavior. let env_rule = SamplingRuleConfig { sample_rate: 0.55, name: Some("env_name".to_string()), @@ -2716,7 +1049,7 @@ mod tests { "tracing_sampling_rules": [] } }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); let rules = config.trace_sampling_rules().to_vec(); assert_eq!(rules.len(), 2, "expected env rule + synthetic catch-all"); @@ -2726,23 +1059,17 @@ mod tests { assert!(rules[1].name.is_none()); } - fn build_config_for_handler_with_target(service: &str, env: &str) -> Arc { - let mut builder = Config::builder(); - builder.set_service(service.to_string()); - builder.set_env(env.to_string()); - Arc::new(builder.build()) - } + // ── service_target gating tests ────────────────────────────────────── #[test] fn test_handler_service_target_match_applies() { - // A config whose service_target matches the tracer's service/env applies. let config = build_config_for_handler_with_target("svc-a", "env-a"); let payload = br#"{ "id": "rc-target-match", "service_target": {"service": "svc-a", "env": "env-a"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 1, @@ -2752,15 +1079,13 @@ mod tests { #[test] fn test_handler_service_target_service_mismatch_ignored() { - // Codex fix: a config targeting a DIFFERENT service must never mutate - // this tracer's sampler state. let config = build_config_for_handler_with_target("svc-a", "env-a"); let payload = br#"{ "id": "rc-other-svc", "service_target": {"service": "svc-b", "env": "env-a"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 0, @@ -2776,7 +1101,7 @@ mod tests { "service_target": {"service": "svc-a", "env": "env-b"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 0, @@ -2786,14 +1111,13 @@ mod tests { #[test] fn test_handler_service_target_wildcard_applies() { - // A wildcard ("*") target applies regardless of the tracer's service/env. let config = build_config_for_handler_with_target("svc-a", "env-a"); let payload = br#"{ "id": "rc-wildcard", "service_target": {"service": "*", "env": "*"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 1, @@ -2803,14 +1127,12 @@ mod tests { #[test] fn test_handler_absent_service_target_applies() { - // Absent service_target (e.g. older payloads) must still apply — the - // target check only gates when a specific, non-wildcard target is set. let config = build_config_for_handler_with_target("svc-a", "env-a"); let payload = br#"{ "id": "rc-no-target", "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 1, @@ -2820,15 +1142,13 @@ mod tests { #[test] fn test_handler_service_target_case_insensitive_applies() { - // service/env case can differ from what the tracer reports (the UI warns - // about this); a case-only difference must still apply, not be skipped. let config = build_config_for_handler_with_target("svc-a", "env-a"); let payload = br#"{ "id": "rc-case", "service_target": {"service": "SVC-A", "env": "ENV-A"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 1, @@ -2838,9 +1158,6 @@ mod tests { #[test] fn test_handler_service_target_extra_service_applies() { - // A config targeting an advertised extra service is legitimately ours and - // must apply (the tracer reports extra_services to the backend, which can - // deliver a config scoped to one of them). let config = build_config_for_handler_with_target("svc-a", "env-a"); config.add_extra_services(["svc-extra"].into_iter()); let payload = br#"{ @@ -2848,7 +1165,7 @@ mod tests { "service_target": {"service": "svc-extra", "env": "*"}, "lib_config": {"tracing_sampling_rate": 0.5} }"#; - ApmTracingHandler.process_config(payload, &config).unwrap(); + apply_payload(payload, &config).unwrap(); assert_eq!( config.trace_sampling_rules().len(), 1, diff --git a/instrumentation/Cargo.lock b/instrumentation/Cargo.lock index 9d757225..f1bc7f6e 100644 --- a/instrumentation/Cargo.lock +++ b/instrumentation/Cargo.lock @@ -17,6 +17,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -89,12 +98,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -171,6 +174,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -244,6 +259,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -339,6 +360,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "datadog-aws-lambda" version = "0.1.0" @@ -359,18 +415,16 @@ version = "0.3.3" dependencies = [ "anyhow", "arc-swap", - "base64 0.21.7", "criterion", "foldhash 0.1.5", "hashbrown 0.15.5", - "http-body-util", "hyper", - "hyper-util", "libc", "libdd-capabilities-impl", "libdd-common", "libdd-data-pipeline", "libdd-library-config", + "libdd-remote-config", "libdd-sampling", "libdd-telemetry", "libdd-tinybytes", @@ -385,13 +439,22 @@ dependencies = [ "rustc_version_runtime", "serde", "serde_json", - "sha2", "thiserror 1.0.69", "tokio", "tokio-util", "uuid", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "digest" version = "0.10.7" @@ -413,6 +476,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.16.0" @@ -627,7 +696,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -645,6 +714,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.5" @@ -780,7 +855,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -797,6 +872,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -885,6 +984,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -906,6 +1011,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -993,7 +1109,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed49669d6430292aead991e19bf13153135a884f916e68f32997c951af637ebe" dependencies = [ "async-stream", - "base64 0.22.1", + "base64", "bytes", "futures", "http", @@ -1056,8 +1172,7 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdd-capabilities" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ce42b411956272ea0ff851dddbd8ea4dae251192ffdbc50e90e20737ffd600" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", @@ -1068,8 +1183,7 @@ dependencies = [ [[package]] name = "libdd-capabilities-impl" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a6210d654b578a52ac4abe31f94b88f52740c8f2a62adc969cad240eae319d" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "bytes", "http", @@ -1082,8 +1196,7 @@ dependencies = [ [[package]] name = "libdd-common" version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af81e6953fab2814792b8893a2cee9f16aad1b71cb3f8720b7256f28d6a2d8d" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", @@ -1113,8 +1226,7 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda4a989dc3fc0ebd0523521f52d18ee72172d9f275762b65a205ec9b960eb9" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", @@ -1133,7 +1245,7 @@ dependencies = [ "libdd-telemetry", "libdd-tinybytes", "libdd-trace-obfuscation", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-stats", "libdd-trace-utils", "rmp-serde", @@ -1149,8 +1261,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b31b2435e2e8eaba0e35a96df3e1407b68f8ef76055383ceb2ba5a09e5a1bb5" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "prost", ] @@ -1158,8 +1269,7 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f79afd4a3f84f80f6b631e4446d09131c156f1179295e390e9b4dc2bfa527cf" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "cadence", @@ -1177,7 +1287,7 @@ checksum = "726e95f0f543da854a24999b16601afd2ca2c6b0c1bdc511bf52c5cd0635f7ad" dependencies = [ "anyhow", "libc", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "memfd", "prost", "rand 0.8.6", @@ -1187,6 +1297,31 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "libdd-remote-config" +version = "0.1.0" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" +dependencies = [ + "anyhow", + "base64", + "futures-util", + "http", + "http-body-util", + "libdd-common", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "manual_future", + "serde", + "serde_json", + "serde_with", + "sha2", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-util", + "tracing", + "uuid", +] + [[package]] name = "libdd-sampling" version = "4.0.0" @@ -1202,8 +1337,7 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9c295f7b55ec78e261537627a75be4e926a94e2b0014e98392b3f98e1957aa" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "async-trait", "futures", @@ -1220,12 +1354,11 @@ dependencies = [ [[package]] name = "libdd-telemetry" version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9626d441cee43418a080cb2fae694d86f62eb965282ff9d0a1c99058658eb3ad" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", + "base64", "bytes", "futures", "hashbrown 0.15.5", @@ -1248,8 +1381,7 @@ dependencies = [ [[package]] name = "libdd-tinybytes" version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97db80e3f3f9d19643ac444d6267951a5dab622ad9828c51149d49150625cfe8" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "serde", ] @@ -1257,23 +1389,21 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab12e095f3589d02ceed76556e7aa8a1b5bc928a900e143b624e2331294e54b5" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", ] [[package]] name = "libdd-trace-obfuscation" version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44197ef58464bd75d002d10e02f702096dc52d68d08b7355860b534a30f8ac5b" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "fluent-uri", "libdd-common", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-utils", "log", "percent-encoding", @@ -1292,11 +1422,20 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "libdd-trace-protobuf" +version = "3.0.2" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" +dependencies = [ + "prost", + "serde", + "serde_bytes", +] + [[package]] name = "libdd-trace-stats" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32bfbd154a6d6a5b452458850adfa1dd3e36d567ad6ae606b16b8bfeb97fa7a" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", @@ -1309,7 +1448,7 @@ dependencies = [ "libdd-ddsketch", "libdd-shared-runtime", "libdd-trace-obfuscation", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "libdd-trace-utils", "rmp-serde", "serde", @@ -1321,24 +1460,23 @@ dependencies = [ [[package]] name = "libdd-trace-utils" version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58340581c2d7f02782e5e0ace77cd42bca18938761f359079a7ff534a24bca99" +source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "futures", "getrandom 0.2.17", "http", "http-body", "http-body-util", - "indexmap", + "indexmap 2.14.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", "libdd-tinybytes", "libdd-trace-normalization", - "libdd-trace-protobuf", + "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", "prost", "rand 0.8.6", "rmp", @@ -1377,6 +1515,15 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "manual_future" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c72f11f1d8e0c453cbd8042dfb83c2b50384f78a5a5d41019627c5f2062ece" +dependencies = [ + "futures-util", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1424,6 +1571,12 @@ dependencies = [ "libc", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -1594,6 +1747,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1800,7 +1959,7 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", @@ -1909,6 +2068,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "semver" version = "1.0.28" @@ -1961,7 +2144,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -1992,13 +2175,44 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -2065,6 +2279,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.117" @@ -2155,6 +2375,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -2232,7 +2483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "bytes", "http", "http-body", @@ -2285,7 +2536,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -2573,7 +2824,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -2586,7 +2837,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", "semver", ] @@ -2618,12 +2869,65 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2806,7 +3110,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.14.0", "prettyplease", "syn", "wasm-metadata", @@ -2837,7 +3141,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -2856,7 +3160,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.14.0", "log", "semver", "serde", diff --git a/instrumentation/Cargo.toml b/instrumentation/Cargo.toml index 55c9c8d7..776b6693 100644 --- a/instrumentation/Cargo.toml +++ b/instrumentation/Cargo.toml @@ -22,3 +22,11 @@ opentelemetry_sdk = { version = "0.31.0", default-features = false } opentelemetry-semantic-conventions = { version = "0.31.0", features = ["semconv_experimental"] } tracing = { version = "0.1", default-features = false } tokio = { version = "1.44.1" } + +[patch.crates-io] +libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-telemetry = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-tinybytes = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } +libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } \ No newline at end of file From 6ab0fb480cf91b3805045bf27a39478cea4819e6 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Mon, 15 Jun 2026 19:45:38 +0200 Subject: [PATCH 2/4] feat: agentless rc --- Cargo.lock | 284 ++++++++++++------ Cargo.toml | 25 +- .../examples/propagator/src/server.rs | 4 +- .../src/core/configuration/configuration.rs | 54 +++- .../src/core/configuration/remote_config.rs | 34 ++- .../configuration/supported_configurations.rs | 9 +- .../src/ddtrace_transform.rs | 17 +- .../src/mappings/transform/mod.rs | 33 +- supported-configurations.json | 25 ++ 9 files changed, 353 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1292532c..44f874ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,8 +293,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -557,6 +559,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "datadog-opentelemetry" version = "0.4.0" @@ -568,7 +576,7 @@ dependencies = [ "datadog-opentelemetry", "foldhash 0.1.5", "hashbrown 0.15.5", - "hyper", + "hyper 1.9.0", "libc", "libdd-capabilities-impl", "libdd-common", @@ -613,6 +621,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derp" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b84cfd9b6fa437e498215e5625e9e3ae3bf9bb54d623028a181c40820db169" +dependencies = [ + "untrusted 0.7.1", +] + [[package]] name = "diff" version = "0.1.13" @@ -689,6 +706,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -888,7 +911,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap 2.14.0", "slab", "tokio", @@ -950,7 +973,7 @@ dependencies = [ "base64", "bytes", "headers-core", - "http", + "http 1.4.0", "httpdate", "mime", "sha1", @@ -962,7 +985,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.4.0", ] [[package]] @@ -983,6 +1006,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.18", +] + [[package]] name = "http" version = "1.4.0" @@ -990,7 +1024,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "itoa", + "itoa 1.0.18", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", ] [[package]] @@ -1000,7 +1045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1011,8 +1056,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1044,9 +1089,9 @@ dependencies = [ "futures-timer", "futures-util", "headers", - "http", + "http 1.4.0", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "path-tree", "regex", @@ -1062,6 +1107,29 @@ dependencies = [ "url", ] +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.18", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1073,11 +1141,11 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", - "itoa", + "itoa 1.0.18", "pin-project-lite", "smallvec", "tokio", @@ -1090,8 +1158,8 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.9.0", "hyper-util", "rustls", "rustls-native-certs", @@ -1106,7 +1174,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper", + "hyper 1.9.0", "hyper-util", "pin-project-lite", "tokio", @@ -1123,14 +1191,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.9.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-layer", @@ -1343,6 +1411,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.18" @@ -1397,21 +1471,19 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdd-capabilities" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", - "http", + "http 1.4.0", "thiserror 1.0.69", ] [[package]] name = "libdd-capabilities-impl" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "bytes", - "http", + "http 1.4.0", "http-body-util", "libdd-capabilities", "libdd-common", @@ -1421,7 +1493,6 @@ dependencies = [ [[package]] name = "libdd-common" version = "4.2.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "bytes", @@ -1431,10 +1502,10 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-rustls", "hyper-util", "libc", @@ -1455,7 +1526,6 @@ dependencies = [ [[package]] name = "libdd-data-pipeline" version = "6.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", @@ -1463,7 +1533,7 @@ dependencies = [ "bytes", "either", "getrandom 0.2.17", - "http", + "http 1.4.0", "http-body-util", "libdd-capabilities", "libdd-capabilities-impl", @@ -1474,7 +1544,7 @@ dependencies = [ "libdd-telemetry", "libdd-tinybytes", "libdd-trace-obfuscation", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", "libdd-trace-stats", "libdd-trace-utils", "rmp-serde", @@ -1490,7 +1560,6 @@ dependencies = [ [[package]] name = "libdd-ddsketch" version = "1.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "prost", ] @@ -1498,11 +1567,10 @@ dependencies = [ [[package]] name = "libdd-dogstatsd-client" version = "3.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "cadence", - "http", + "http 1.4.0", "libdd-common", "serde", "tracing", @@ -1511,12 +1579,10 @@ dependencies = [ [[package]] name = "libdd-library-config" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726e95f0f543da854a24999b16601afd2ca2c6b0c1bdc511bf52c5cd0635f7ad" dependencies = [ "anyhow", "libc", - "libdd-trace-protobuf 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libdd-trace-protobuf", "memfd", "prost", "rand 0.8.6", @@ -1529,16 +1595,20 @@ dependencies = [ [[package]] name = "libdd-remote-config" version = "0.1.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "base64", + "futures", "futures-util", - "http", + "hashbrown 0.15.5", + "http 1.4.0", "http-body-util", + "libdd-capabilities", + "libdd-capabilities-impl", "libdd-common", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", "manual_future", + "prost", "serde", "serde_json", "serde_with", @@ -1548,14 +1618,13 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tuf", "uuid", ] [[package]] name = "libdd-sampling" version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4a7bf5fe62282784cd6ae4c805b2e35f0520c3464ca090f49b3c9794d4fd4d" dependencies = [ "libdd-common", "lru", @@ -1566,7 +1635,6 @@ dependencies = [ [[package]] name = "libdd-shared-runtime" version = "1.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "async-trait", "futures", @@ -1583,7 +1651,6 @@ dependencies = [ [[package]] name = "libdd-telemetry" version = "5.0.1" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "async-trait", @@ -1591,7 +1658,7 @@ dependencies = [ "bytes", "futures", "hashbrown 0.15.5", - "http", + "http 1.4.0", "http-body-util", "libc", "libdd-common", @@ -1610,7 +1677,6 @@ dependencies = [ [[package]] name = "libdd-tinybytes" version = "1.1.1" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "serde", ] @@ -1618,21 +1684,19 @@ dependencies = [ [[package]] name = "libdd-trace-normalization" version = "2.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", ] [[package]] name = "libdd-trace-obfuscation" version = "4.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "fluent-uri", "libdd-common", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", "libdd-trace-utils", "log", "percent-encoding", @@ -1643,18 +1707,6 @@ dependencies = [ [[package]] name = "libdd-trace-protobuf" version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c67e3e9a09dab3f19d4f79006f3efb8b4bf8416551465edaeeb71816949e2197" -dependencies = [ - "prost", - "serde", - "serde_bytes", -] - -[[package]] -name = "libdd-trace-protobuf" -version = "3.0.2" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "prost", "serde", @@ -1664,20 +1716,19 @@ dependencies = [ [[package]] name = "libdd-trace-stats" version = "5.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "arc-swap", "async-trait", "hashbrown 0.15.5", - "http", + "http 1.4.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", "libdd-ddsketch", "libdd-shared-runtime", "libdd-trace-obfuscation", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", "libdd-trace-utils", "rmp-serde", "serde", @@ -1689,7 +1740,6 @@ dependencies = [ [[package]] name = "libdd-trace-utils" version = "8.0.0" -source = "git+https://github.com/DataDog/libdatadog?rev=2e6214d80#2e6214d80fb3b7055569cdd3bada279f2f723b09" dependencies = [ "anyhow", "base64", @@ -1698,18 +1748,18 @@ dependencies = [ "cargo_metadata", "futures", "getrandom 0.2.17", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "httpmock", - "hyper", + "hyper 1.9.0", "indexmap 2.14.0", "libdd-capabilities", "libdd-capabilities-impl", "libdd-common", "libdd-tinybytes", "libdd-trace-normalization", - "libdd-trace-protobuf 3.0.2 (git+https://github.com/DataDog/libdatadog?rev=2e6214d80)", + "libdd-trace-protobuf", "prost", "rand 0.8.6", "rmp", @@ -1918,7 +1968,7 @@ checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", - "http", + "http 1.4.0", "opentelemetry", "reqwest", ] @@ -1929,7 +1979,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ - "http", + "http 1.4.0", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -2149,7 +2199,7 @@ version = "0.4.0" dependencies = [ "datadog-opentelemetry", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-util", "opentelemetry", "opentelemetry-appender-tracing", @@ -2223,7 +2273,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -2260,7 +2310,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.3", "tracing", "windows-sys 0.52.0", ] @@ -2443,10 +2493,10 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-rustls", "hyper-util", "js-sys", @@ -2482,7 +2532,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -2601,7 +2651,7 @@ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2744,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "indexmap 2.14.0", - "itoa", + "itoa 1.0.18", "memchr", "serde", "serde_core", @@ -2768,7 +2818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.18", "ryu", "serde", ] @@ -2811,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap 2.14.0", - "itoa", + "itoa 1.0.18", "ryu", "serde", "unsafe-libyaml", @@ -2890,6 +2940,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.3" @@ -3001,6 +3061,19 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "test-case" version = "3.3.1" @@ -3090,7 +3163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", - "itoa", + "itoa 1.0.18", "num-conv", "powerfmt", "serde_core", @@ -3161,7 +3234,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -3220,10 +3293,10 @@ dependencies = [ "async-trait", "base64", "bytes", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.9.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -3288,8 +3361,8 @@ dependencies = [ "bitflags", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -3393,6 +3466,31 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tuf" +version = "0.3.0-beta10" +source = "git+https://github.com/DataDog/rust-tuf/?tag=0.3.0-beta10-opw-3#9e8d6077b0e67f13233ad0a347bb7d640705da04" +dependencies = [ + "chrono", + "data-encoding", + "derp", + "futures-io", + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "itoa 0.4.8", + "log", + "percent-encoding", + "ring", + "serde", + "serde_derive", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "untrusted 0.7.1", + "url", +] + [[package]] name = "typenum" version = "1.20.0" @@ -3429,6 +3527,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 932c1e1e..43b80503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,19 +68,6 @@ hashbrown = { version = "0.15.5" } foldhash = { version = "0.1.5" } -# libdd-remote-config depends on libdd-common and (optionally) libdd-trace-protobuf -# via path+version. Redirect their crates.io copies to the same libdatadog revision -# so cargo unifies the graph (otherwise we get duplicate libdd-common copies and -# type-mismatch errors at the boundary). Remove once libdd-remote-config is on -# crates.io with matching libdd-* versions. -[patch.crates-io] -libdd-data-pipeline = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } -libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } -libdd-telemetry = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } -libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } -libdd-tinybytes = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } -libdd-capabilities-impl = { git = "https://github.com/DataDog/libdatadog", rev = "2e6214d80" } - [profile.dev] debug = 2 # full debug info @@ -90,3 +77,15 @@ debug = false incremental = false opt-level = 3 +[patch.crates-io] +libdd-data-pipeline = { path = "../libdatadog/libdd-data-pipeline" } +libdd-trace-utils = { path = "../libdatadog/libdd-trace-utils" } +libdd-capabilities-impl = { path = "../libdatadog/libdd-capabilities-impl" } +libdd-telemetry = { path = "../libdatadog/libdd-telemetry" } +libdd-common = { path = "../libdatadog/libdd-common" } +libdd-tinybytes = { path = "../libdatadog/libdd-tinybytes" } +libdd-library-config = { path = "../libdatadog/libdd-library-config" } +libdd-sampling = { path = "../libdatadog/libdd-sampling" } + +[patch."https://github.com/DataDog/libdatadog"] +libdd-remote-config = { path = "../libdatadog/libdd-remote-config" } diff --git a/datadog-opentelemetry/examples/propagator/src/server.rs b/datadog-opentelemetry/examples/propagator/src/server.rs index 2ed563d0..5a9eae61 100644 --- a/datadog-opentelemetry/examples/propagator/src/server.rs +++ b/datadog-opentelemetry/examples/propagator/src/server.rs @@ -242,8 +242,8 @@ fn init_logs() -> SdkLoggerProvider { .with_log_processor(EnrichWithBaggageLogProcessor) .with_simple_exporter(LogExporter::default()) .build(); - let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); - tracing_subscriber::registry().with(otel_layer).init(); + // let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); + tracing_subscriber::registry().with(tracing_subscriber::fmt::layer()).init(); logger_provider } diff --git a/datadog-opentelemetry/src/core/configuration/configuration.rs b/datadog-opentelemetry/src/core/configuration/configuration.rs index 6bcf1602..47a56a6c 100644 --- a/datadog-opentelemetry/src/core/configuration/configuration.rs +++ b/datadog-opentelemetry/src/core/configuration/configuration.rs @@ -90,7 +90,8 @@ impl Default for RemoteConfigCallbacks { Self::new() } } -pub const TRACER_VERSION: &str = env!("CARGO_PKG_VERSION"); +// pub const TRACER_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const TRACER_VERSION: &str = "4.10.4"; const DATADOG_TAGS_MAX_LENGTH: usize = 512; const RC_DEFAULT_POLL_INTERVAL: f64 = 5.0; // 5 seconds is the highest interval allowed by the spec @@ -1086,6 +1087,15 @@ pub struct Config { /// Which baggage keys are promoted to span tags with a `"baggage."` prefix. trace_baggage_tag_keys: ConfigItem, + /// Datadog API key used for agentless mode + api_key: ConfigItem>, + + /// Datadog site (e.g. "datadoghq.com") used for agentless mode + site: ConfigItem>, + + /// Enables agentless remote config when set together with DD_API_KEY and DD_SITE + experimental_agentless_enabled: ConfigItem, + /// Whether remote configuration is enabled remote_config_enabled: ConfigItem, @@ -1287,6 +1297,10 @@ impl Config { #[cfg(feature = "test-utils")] wait_agent_info_ready: default.wait_agent_info_ready, extra_services_tracker: ExtraServicesTracker::new(), + api_key: cisu.update_non_empty_string(default.api_key, Some), + site: cisu.update_non_empty_string(default.site, Cow::Owned), + experimental_agentless_enabled: cisu + .update_parsed(default.experimental_agentless_enabled), remote_config_enabled: cisu.update_parsed(default.remote_config_enabled), remote_config_poll_interval: cisu.update_parsed_with_transform( default.remote_config_poll_interval, @@ -1358,6 +1372,8 @@ impl Config { &self.trace_propagation_style_inject, &self.trace_propagation_extract_first, &self.trace_baggage_tag_keys, + &self.site, + &self.experimental_agentless_enabled, &self.remote_config_enabled, &self.remote_config_poll_interval, &self.datadog_tags_max_length, @@ -1834,6 +1850,29 @@ impl Config { || self.extra_services_tracker.contains_service(target_service) } + /// Returns the Datadog API key if set. + pub fn api_key(&self) -> Option<&str> { + self.api_key.value().as_deref() + } + + /// Returns the Datadog site (e.g. `"datadoghq.com"`). + pub fn site(&self) -> &str { + self.site.value().as_ref() + } + + /// Returns `Some((api_key, site_url))` when all three agentless settings are + /// set — `DD_API_KEY`, `DD_SITE`, and `_DD_EXPERIMENTAL_AGENTLESS_ENABLED` — + /// allowing the remote config client to operate without a local agent. + /// The returned `site_url` is `https://{site}`. + pub(crate) fn agentless_rc_endpoint(&self) -> Option<(String, String)> { + if !*self.experimental_agentless_enabled.value() { + return None; + } + let api_key = self.api_key()?.to_owned(); + let site_url = format!("https://{}", self.site()); + Some((api_key, site_url)) + } + /// Check if remote configuration is enabled pub fn remote_config_enabled(&self) -> bool { *self.remote_config_enabled.value() @@ -1988,8 +2027,8 @@ fn default_config() -> Config { LevelFilter::default(), ), tracer_version: TRACER_VERSION, - language: "rust", - language_version: version().to_string(), + language: "python", + language_version: "3.10.1".to_string(), // version().to_string(), trace_stats_computation_enabled: ConfigItem::new( SupportedConfigurations::DD_TRACE_STATS_COMPUTATION_ENABLED, true, @@ -2053,6 +2092,15 @@ fn default_config() -> Config { ]), ), extra_services_tracker: ExtraServicesTracker::new(), + api_key: ConfigItem::new(SupportedConfigurations::DD_API_KEY, None), + site: ConfigItem::new( + SupportedConfigurations::DD_SITE, + Cow::Borrowed("datadoghq.com"), + ), + experimental_agentless_enabled: ConfigItem::new( + SupportedConfigurations::_DD_EXPERIMENTAL_AGENTLESS_ENABLED, + false, + ), remote_config_enabled: ConfigItem::new( SupportedConfigurations::DD_REMOTE_CONFIGURATION_ENABLED, true, diff --git a/datadog-opentelemetry/src/core/configuration/remote_config.rs b/datadog-opentelemetry/src/core/configuration/remote_config.rs index 4de8568b..4760a838 100644 --- a/datadog-opentelemetry/src/core/configuration/remote_config.rs +++ b/datadog-opentelemetry/src/core/configuration/remote_config.rs @@ -26,6 +26,7 @@ use libdd_remote_config::parse::{ }; use libdd_remote_config::{RemoteConfigCapabilities, RemoteConfigProduct, Target}; use serde::Deserialize; +use std::borrow::Cow; use std::sync::Arc; use std::thread::{self}; use std::time::Duration; @@ -389,11 +390,14 @@ impl RemoteConfigClientWorker { let target = build_target(&self.config); let runtime_id = self.config.runtime_id().to_string(); + let agentless_enabled = self.config.agentless_rc_endpoint().is_some(); let options = ConfigOptions { invariants: ConfigInvariants { language: self.config.language().to_string(), tracer_version: self.config.tracer_version().to_string(), endpoint: self.endpoint.clone(), + hostname: HOSTNAME.clone(), + agentless_enabled, }, products: vec![RemoteConfigProduct::ApmTracing], capabilities: vec![ @@ -402,7 +406,14 @@ impl RemoteConfigClientWorker { ], }; - let mut fetcher = SingleChangesFetcher::new(storage, target, runtime_id, options); + let mut fetcher = + match SingleChangesFetcher::new(storage, target, runtime_id, options).await { + Ok(f) => f, + Err(e_) => { + crate::dd_debug!("RemoteConfigClient: init failed {}", e_); + return; + } + }; loop { fetcher.set_extra_services(self.config.get_extra_services()); @@ -420,7 +431,28 @@ impl RemoteConfigClientWorker { } } +/// The machine hostname, resolved once at first use by invoking the `hostname` CLI. +/// Falls back to an empty string on any error. +static HOSTNAME: std::sync::LazyLock = std::sync::LazyLock::new(|| { + std::process::Command::new("hostname") + .output() + .ok() + .and_then(|out| String::from_utf8(out.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_default() +}); + fn build_endpoint(config: &Config) -> Result { + if let Some((api_key, site_url)) = config.agentless_rc_endpoint() { + let url = libdd_common::parse_uri(&site_url) + .map_err(|_| RemoteConfigClientError::InvalidAgentUri)?; + return Ok(Endpoint { + url, + api_key: Some(Cow::Owned(api_key)), + timeout_ms: FETCH_TIMEOUT_MS, + ..Default::default() + }); + } let agent_url = libdd_common::parse_uri(&config.trace_agent_url()) .map_err(|_| RemoteConfigClientError::InvalidAgentUri)?; Ok(Endpoint { diff --git a/datadog-opentelemetry/src/core/configuration/supported_configurations.rs b/datadog-opentelemetry/src/core/configuration/supported_configurations.rs index e2bfa211..1d7bc7ba 100644 --- a/datadog-opentelemetry/src/core/configuration/supported_configurations.rs +++ b/datadog-opentelemetry/src/core/configuration/supported_configurations.rs @@ -9,6 +9,7 @@ #[non_exhaustive] pub(crate) enum SupportedConfigurations { DD_AGENT_HOST, + DD_API_KEY, DD_DOGSTATSD_HOST, DD_DOGSTATSD_PORT, DD_DOGSTATSD_URL, @@ -20,6 +21,7 @@ pub(crate) enum SupportedConfigurations { DD_REMOTE_CONFIGURATION_ENABLED, DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS, DD_SERVICE, + DD_SITE, DD_TAGS, DD_TELEMETRY_HEARTBEAT_INTERVAL, DD_TELEMETRY_LOG_COLLECTION_ENABLED, @@ -57,6 +59,7 @@ pub(crate) enum SupportedConfigurations { OTEL_METRIC_EXPORT_INTERVAL, OTEL_METRIC_EXPORT_TIMEOUT, OTEL_RESOURCE_ATTRIBUTES, + _DD_EXPERIMENTAL_AGENTLESS_ENABLED, _DD_TRACE_STATS_COMPUTATION_EXPERIMENTAL_CLIENT_OBFUSCATION_ENABLED, /// Used for testing purposes only @@ -78,6 +81,7 @@ impl SupportedConfigurations { pub fn as_str(&self) -> &'static str { match self { SupportedConfigurations::DD_AGENT_HOST => "DD_AGENT_HOST", + SupportedConfigurations::DD_API_KEY => "DD_API_KEY", SupportedConfigurations::DD_DOGSTATSD_HOST => "DD_DOGSTATSD_HOST", SupportedConfigurations::DD_DOGSTATSD_PORT => "DD_DOGSTATSD_PORT", SupportedConfigurations::DD_DOGSTATSD_URL => "DD_DOGSTATSD_URL", @@ -89,6 +93,7 @@ impl SupportedConfigurations { SupportedConfigurations::DD_REMOTE_CONFIGURATION_ENABLED => "DD_REMOTE_CONFIGURATION_ENABLED", SupportedConfigurations::DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS => "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS", SupportedConfigurations::DD_SERVICE => "DD_SERVICE", + SupportedConfigurations::DD_SITE => "DD_SITE", SupportedConfigurations::DD_TAGS => "DD_TAGS", SupportedConfigurations::DD_TELEMETRY_HEARTBEAT_INTERVAL => "DD_TELEMETRY_HEARTBEAT_INTERVAL", SupportedConfigurations::DD_TELEMETRY_LOG_COLLECTION_ENABLED => "DD_TELEMETRY_LOG_COLLECTION_ENABLED", @@ -126,6 +131,7 @@ impl SupportedConfigurations { SupportedConfigurations::OTEL_METRIC_EXPORT_INTERVAL => "OTEL_METRIC_EXPORT_INTERVAL", SupportedConfigurations::OTEL_METRIC_EXPORT_TIMEOUT => "OTEL_METRIC_EXPORT_TIMEOUT", SupportedConfigurations::OTEL_RESOURCE_ATTRIBUTES => "OTEL_RESOURCE_ATTRIBUTES", + SupportedConfigurations::_DD_EXPERIMENTAL_AGENTLESS_ENABLED => "_DD_EXPERIMENTAL_AGENTLESS_ENABLED", SupportedConfigurations::_DD_TRACE_STATS_COMPUTATION_EXPERIMENTAL_CLIENT_OBFUSCATION_ENABLED => "_DD_TRACE_STATS_COMPUTATION_EXPERIMENTAL_CLIENT_OBFUSCATION_ENABLED", #[cfg(test)] SupportedConfigurations::DD_COMPLEX_STRUCT => "DD_COMPLEX_STRUCT", @@ -164,7 +170,8 @@ impl SupportedConfigurations { pub fn is_sensitive(&self) -> bool { matches!( self, - SupportedConfigurations::OTEL_EXPORTER_OTLP_HEADERS + SupportedConfigurations::DD_API_KEY + | SupportedConfigurations::OTEL_EXPORTER_OTLP_HEADERS | SupportedConfigurations::OTEL_EXPORTER_OTLP_LOGS_HEADERS | SupportedConfigurations::OTEL_EXPORTER_OTLP_METRICS_HEADERS ) diff --git a/datadog-opentelemetry/src/ddtrace_transform.rs b/datadog-opentelemetry/src/ddtrace_transform.rs index 6024b527..a5b42118 100644 --- a/datadog-opentelemetry/src/ddtrace_transform.rs +++ b/datadog-opentelemetry/src/ddtrace_transform.rs @@ -4,8 +4,6 @@ //! This module contains trace mapping from otel to datadog //! specific to datadog-opentelemetry -use std::collections::hash_map; - use crate::{ core::sampling, mappings::{ @@ -28,15 +26,18 @@ fn otel_sampling_to_dd_sampling( otel_trace_flags: opentelemetry::trace::TraceFlags, dd_span: &mut DdSpan, ) { - if let hash_map::Entry::Vacant(e) = dd_span + if !dd_span .metrics - .entry(SpanStr::from_static_str("_sampling_priority_v1")) + .contains_key(&SpanStr::from_static_str("_sampling_priority_v1")) { - if otel_trace_flags.is_sampled() { - e.insert(sampling::priority::AUTO_KEEP.into_i8() as f64); + let priority = if otel_trace_flags.is_sampled() { + sampling::priority::AUTO_KEEP.into_i8() as f64 } else { - e.insert(sampling::priority::AUTO_REJECT.into_i8() as f64); - } + sampling::priority::AUTO_REJECT.into_i8() as f64 + }; + dd_span + .metrics + .insert(SpanStr::from_static_str("_sampling_priority_v1"), priority); } } diff --git a/datadog-opentelemetry/src/mappings/transform/mod.rs b/datadog-opentelemetry/src/mappings/transform/mod.rs index 727b0f03..dbd8ced6 100644 --- a/datadog-opentelemetry/src/mappings/transform/mod.rs +++ b/datadog-opentelemetry/src/mappings/transform/mod.rs @@ -42,12 +42,9 @@ pub mod transform_tests; use attribute_keys::*; use otel_util::*; -use std::{ - borrow::{Borrow, Cow}, - collections::{hash_map, HashMap}, -}; +use std::borrow::{Borrow, Cow}; -use libdd_trace_utils::span::SpanText; +use libdd_trace_utils::span::{v04::VecMap, SpanText}; use opentelemetry::{ trace::{Link, SpanKind}, Key, KeyValue, Value, @@ -161,13 +158,13 @@ fn otel_span_to_dd_span_minimal<'a>( parent_id, start, duration, - meta: HashMap::with_capacity(span.attr_len() + span.res_len()), + meta: VecMap::with_capacity(span.attr_len() + span.res_len()), // We will likely put _sampling_priority, maybe _dd.measured, top level // And by default tracing tags by code.line.number, thread.id and busy_ns/idle_ns // // Why 6? This seems like a good number to prevent small reallocations while not // using too much memory - metrics: HashMap::with_capacity(6), + metrics: VecMap::with_capacity(6), ..Default::default() }; if let Some(error) = span.get_attr_num(DATADOG_ERROR) { @@ -283,10 +280,12 @@ fn status_to_error(status: &opentelemetry::trace::Status, dd_span: &mut DdSpan) } } let error_msg_key = SpanStr::from_static_str("error.message"); - if let hash_map::Entry::Vacant(error_msg_slot) = dd_span.meta.entry(error_msg_key.clone()) { + if !dd_span.meta.contains_key(&error_msg_key) { match status { opentelemetry::trace::Status::Error { description, .. } if !description.is_empty() => { - error_msg_slot.insert(SpanStr::from_cow(description.clone())); + dd_span + .meta + .insert(error_msg_key, SpanStr::from_cow(description.clone())); } _ => { for key in ["http.response.status_code", "http.status_code"] { @@ -590,15 +589,19 @@ pub fn otel_span_to_dd_span<'a>( SpanStr::from_string(otel_trace_id), ); - if let hash_map::Entry::Vacant(version_slot) = - dd_span.meta.entry(SpanStr::from_static_str("version")) + if !dd_span + .meta + .contains_key(&SpanStr::from_static_str("version")) { let version = otel_resource .get(&Key::from_static_str(SERVICE_VERSION.key())) .map(|v| v.to_string()) .unwrap_or_default(); if !version.is_empty() { - version_slot.insert(SpanStr::from_string(version)); + dd_span.meta.insert( + SpanStr::from_static_str("version"), + SpanStr::from_string(version), + ); } } @@ -620,10 +623,12 @@ pub fn otel_span_to_dd_span<'a>( } } - if let hash_map::Entry::Vacant(env_slot) = dd_span.meta.entry(SpanStr::from_static_str("env")) { + if !dd_span.meta.contains_key(&SpanStr::from_static_str("env")) { let env = get_otel_env(&span_extracted); if !env.is_empty() { - env_slot.insert(SpanStr::from_cow(env)); + dd_span + .meta + .insert(SpanStr::from_static_str("env"), SpanStr::from_cow(env)); } } diff --git a/supported-configurations.json b/supported-configurations.json index 66cf16bc..b0ee03ef 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -10,6 +10,15 @@ "propertyKeys": ["agent_host"] } ], + "DD_API_KEY": [ + { + "version": "A", + "type": "string", + "default": "", + "propertyKeys": ["api_key"], + "sensitive": true + } + ], "DD_DOGSTATSD_HOST": [ { "version": "A", @@ -99,6 +108,14 @@ "propertyKeys": ["service"] } ], + "DD_SITE": [ + { + "version": "A", + "type": "string", + "default": "datadoghq.com", + "propertyKeys": ["site"] + } + ], "DD_TAGS": [ { "version": "A", @@ -398,6 +415,14 @@ "propertyKeys": [] } ], + "_DD_EXPERIMENTAL_AGENTLESS_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "false", + "propertyKeys": ["experimental_agentless_enabled"] + } + ], "_DD_TRACE_STATS_COMPUTATION_EXPERIMENTAL_CLIENT_OBFUSCATION_ENABLED": [ { "version": "B", From 50ca35838bb116f37804d07aeac9f4df99da9e30 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Fri, 19 Jun 2026 17:16:22 +0200 Subject: [PATCH 3/4] fix: update new API --- .../src/core/configuration/remote_config.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/datadog-opentelemetry/src/core/configuration/remote_config.rs b/datadog-opentelemetry/src/core/configuration/remote_config.rs index 4760a838..7284af3d 100644 --- a/datadog-opentelemetry/src/core/configuration/remote_config.rs +++ b/datadog-opentelemetry/src/core/configuration/remote_config.rs @@ -13,6 +13,7 @@ use crate::core::configuration::Config; use crate::core::utils::{ShutdownSignaler, WorkerHandle}; use anyhow::Result; +use libdd_remote_config::agentless_client::AgentlessConfig; use core::fmt; use libdd_common::tag::Tag; use libdd_common::Endpoint; @@ -396,13 +397,24 @@ impl RemoteConfigClientWorker { language: self.config.language().to_string(), tracer_version: self.config.tracer_version().to_string(), endpoint: self.endpoint.clone(), - hostname: HOSTNAME.clone(), - agentless_enabled, + agentless: agentless_enabled.then(|| AgentlessConfig { + hostname: HOSTNAME.clone(), + config_root_override_path: None, + director_root_override_path: None, + agent_uuid: None, + }) }, - products: vec![RemoteConfigProduct::ApmTracing], + products: vec![ + RemoteConfigProduct::ApmTracing, + RemoteConfigProduct::LiveDebugger, + ], capabilities: vec![ RemoteConfigCapabilities::ApmTracingSampleRate, RemoteConfigCapabilities::ApmTracingSampleRules, + RemoteConfigCapabilities::ApmTracingEnabled, + RemoteConfigCapabilities::ApmTracingCustomTags, + RemoteConfigCapabilities::ApmTracingEnableDynamicInstrumentation, + RemoteConfigCapabilities::ApmTracingEnableLiveDebugging, ], }; From 9c50263aa8a943c780af71419cc85397da68fff7 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Mon, 22 Jun 2026 18:36:54 +0200 Subject: [PATCH 4/4] fix: bump to latest --- Cargo.lock | 341 ++++++++++++++---- .../src/core/configuration/remote_config.rs | 5 +- 2 files changed, 264 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44f874ee..6d5a5f3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -373,6 +379,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -576,10 +592,10 @@ dependencies = [ "datadog-opentelemetry", "foldhash 0.1.5", "hashbrown 0.15.5", - "hyper 1.9.0", + "hyper", "libc", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 4.2.0", "libdd-data-pipeline", "libdd-library-config", "libdd-remote-config", @@ -1027,17 +1043,6 @@ dependencies = [ "itoa 1.0.18", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1057,7 +1062,7 @@ dependencies = [ "bytes", "futures-core", "http 1.4.0", - "http-body 1.0.1", + "http-body", "pin-project-lite", ] @@ -1091,7 +1096,7 @@ dependencies = [ "headers", "http 1.4.0", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "path-tree", "regex", @@ -1107,29 +1112,6 @@ dependencies = [ "url", ] -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa 1.0.18", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.9.0" @@ -1142,7 +1124,7 @@ dependencies = [ "futures-core", "h2", "http 1.4.0", - "http-body 1.0.1", + "http-body", "httparse", "httpdate", "itoa 1.0.18", @@ -1159,7 +1141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", - "hyper 1.9.0", + "hyper", "hyper-util", "rustls", "rustls-native-certs", @@ -1174,7 +1156,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.9.0", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -1192,13 +1174,13 @@ dependencies = [ "futures-channel", "futures-util", "http 1.4.0", - "http-body 1.0.1", - "hyper 1.9.0", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "system-configuration", "tokio", "tower-layer", @@ -1423,6 +1405,50 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.95" @@ -1486,13 +1512,48 @@ dependencies = [ "http 1.4.0", "http-body-util", "libdd-capabilities", - "libdd-common", + "libdd-common 5.0.0", "tokio", ] [[package]] name = "libdd-common" version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af81e6953fab2814792b8893a2cee9f16aad1b71cb3f8720b7256f28d6a2d8d" +dependencies = [ + "anyhow", + "bytes", + "cc", + "const_format", + "futures", + "futures-core", + "futures-util", + "hex", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "libc", + "nix", + "pin-project", + "regex", + "rustls", + "rustls-native-certs", + "serde", + "static_assertions", + "thiserror 1.0.69", + "tokio", + "tokio-rustls", + "tower-service", + "windows-sys 0.52.0", +] + +[[package]] +name = "libdd-common" +version = "5.0.0" dependencies = [ "anyhow", "bytes", @@ -1503,9 +1564,9 @@ dependencies = [ "futures-util", "hex", "http 1.4.0", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-rustls", "hyper-util", "libc", @@ -1514,6 +1575,7 @@ dependencies = [ "regex", "rustls", "rustls-native-certs", + "rustls-platform-verifier", "serde", "static_assertions", "thiserror 1.0.69", @@ -1537,12 +1599,13 @@ dependencies = [ "http-body-util", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 5.0.0", "libdd-ddsketch", "libdd-dogstatsd-client", "libdd-shared-runtime", "libdd-telemetry", "libdd-tinybytes", + "libdd-trace-normalization", "libdd-trace-obfuscation", "libdd-trace-protobuf", "libdd-trace-stats", @@ -1571,7 +1634,7 @@ dependencies = [ "anyhow", "cadence", "http 1.4.0", - "libdd-common", + "libdd-common 5.0.0", "serde", "tracing", ] @@ -1594,7 +1657,7 @@ dependencies = [ [[package]] name = "libdd-remote-config" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "base64", @@ -1605,7 +1668,7 @@ dependencies = [ "http-body-util", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 5.0.0", "libdd-trace-protobuf", "manual_future", "prost", @@ -1613,6 +1676,8 @@ dependencies = [ "serde_json", "serde_with", "sha2", + "strum", + "strum_macros", "thiserror 2.0.18", "time", "tokio", @@ -1626,7 +1691,7 @@ dependencies = [ name = "libdd-sampling" version = "4.0.0" dependencies = [ - "libdd-common", + "libdd-common 5.0.0", "lru", "serde", "serde_json", @@ -1641,7 +1706,7 @@ dependencies = [ "futures-util", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 5.0.0", "tokio", "tokio-util", "tracing", @@ -1661,7 +1726,7 @@ dependencies = [ "http 1.4.0", "http-body-util", "libc", - "libdd-common", + "libdd-common 5.0.0", "libdd-ddsketch", "libdd-shared-runtime", "serde", @@ -1695,7 +1760,7 @@ version = "4.0.0" dependencies = [ "anyhow", "fluent-uri", - "libdd-common", + "libdd-common 5.0.0", "libdd-trace-protobuf", "libdd-trace-utils", "log", @@ -1724,7 +1789,7 @@ dependencies = [ "http 1.4.0", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 5.0.0", "libdd-ddsketch", "libdd-shared-runtime", "libdd-trace-obfuscation", @@ -1749,14 +1814,14 @@ dependencies = [ "futures", "getrandom 0.2.17", "http 1.4.0", - "http-body 1.0.1", + "http-body", "http-body-util", "httpmock", - "hyper 1.9.0", + "hyper", "indexmap 2.14.0", "libdd-capabilities", "libdd-capabilities-impl", - "libdd-common", + "libdd-common 5.0.0", "libdd-tinybytes", "libdd-trace-normalization", "libdd-trace-protobuf", @@ -1765,8 +1830,10 @@ dependencies = [ "rmp", "rmp-serde", "rmpv", + "rustc-hash", "serde", "serde_json", + "thin-vec", "tokio", "tracing", "urlencoding", @@ -2199,7 +2266,7 @@ version = "0.4.0" dependencies = [ "datadog-opentelemetry", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-util", "opentelemetry", "opentelemetry-appender-tracing", @@ -2273,7 +2340,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -2310,7 +2377,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.52.0", ] @@ -2494,9 +2561,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.4.0", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-rustls", "hyper-util", "js-sys", @@ -2643,6 +2710,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.13" @@ -2940,16 +3034,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -2984,6 +3068,25 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3107,6 +3210,12 @@ dependencies = [ "test-case-core", ] +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + [[package]] name = "thiserror" version = "1.0.69" @@ -3234,7 +3343,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3294,9 +3403,9 @@ dependencies = [ "base64", "bytes", "http 1.4.0", - "http-body 1.0.1", + "http-body", "http-body-util", - "hyper 1.9.0", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", @@ -3362,7 +3471,7 @@ dependencies = [ "bytes", "futures-util", "http 1.4.0", - "http-body 1.0.1", + "http-body", "iri-string", "pin-project-lite", "tower", @@ -3477,7 +3586,6 @@ dependencies = [ "futures-io", "futures-util", "http 0.2.12", - "hyper 0.14.32", "itoa 0.4.8", "log", "percent-encoding", @@ -3744,6 +3852,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3854,6 +3971,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3872,6 +3998,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3903,6 +4044,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3915,6 +4062,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3927,6 +4080,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3945,6 +4104,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3957,6 +4122,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3969,6 +4140,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3981,6 +4158,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/datadog-opentelemetry/src/core/configuration/remote_config.rs b/datadog-opentelemetry/src/core/configuration/remote_config.rs index 7284af3d..883dbcda 100644 --- a/datadog-opentelemetry/src/core/configuration/remote_config.rs +++ b/datadog-opentelemetry/src/core/configuration/remote_config.rs @@ -13,12 +13,11 @@ use crate::core::configuration::Config; use crate::core::utils::{ShutdownSignaler, WorkerHandle}; use anyhow::Result; -use libdd_remote_config::agentless_client::AgentlessConfig; use core::fmt; use libdd_common::tag::Tag; use libdd_common::Endpoint; use libdd_remote_config::fetch::{ - ConfigApplyState, ConfigInvariants, ConfigOptions, SingleChangesFetcher, + AgentlessConfig, ConfigApplyState, ConfigInvariants, ConfigOptions, SingleChangesFetcher, }; use libdd_remote_config::file_change_tracker::Change; use libdd_remote_config::file_storage::ParsedFileStorage; @@ -402,7 +401,7 @@ impl RemoteConfigClientWorker { config_root_override_path: None, director_root_override_path: None, agent_uuid: None, - }) + }), }, products: vec![ RemoteConfigProduct::ApmTracing,