1pub mod tracer_metadata;
4
5use std::borrow::Cow;
6use std::cell::OnceCell;
7use std::collections::HashMap;
8use std::ops::Deref;
9use std::path::Path;
10use std::{env, fs, io, mem};
11
12struct MatchMaps<'a> {
22 tags: &'a HashMap<String, String>,
23 env_map: OnceCell<HashMap<&'a str, &'a str>>,
24 args_map: OnceCell<HashMap<&'a str, &'a str>>,
25}
26
27impl<'a> MatchMaps<'a> {
28 fn env(&self, process_info: &'a ProcessInfo) -> &HashMap<&'a str, &'a str> {
29 self.env_map.get_or_init(|| {
30 let mut map = HashMap::new();
31 for e in &process_info.envp {
32 let Ok(s) = std::str::from_utf8(e.deref()) else {
33 continue;
34 };
35 let (k, v) = match s.split_once('=') {
36 Some((k, v)) => (k, v),
37 None => (s, ""),
38 };
39 map.insert(k, v);
40 }
41 map
42 })
43 }
44
45 fn args(&self, process_info: &'a ProcessInfo) -> &HashMap<&str, &str> {
46 self.args_map.get_or_init(|| {
47 let mut map = HashMap::new();
48 let mut args = process_info.args.iter().peekable();
49 loop {
50 let Some(arg) = args.next() else {
51 break;
52 };
53 let Ok(arg) = std::str::from_utf8(arg.deref()) else {
54 continue;
55 };
56 if let Some((k, v)) = arg.split_once('=') {
58 map.insert(k, v);
59 continue;
60 }
61 }
62 map
63 })
64 }
65}
66
67struct Matcher<'a> {
68 process_info: &'a ProcessInfo,
69 match_maps: MatchMaps<'a>,
70}
71
72impl<'a> Matcher<'a> {
73 fn new(process_info: &'a ProcessInfo, tags: &'a HashMap<String, String>) -> Self {
74 Self {
75 process_info,
76 match_maps: MatchMaps {
77 tags,
78 env_map: OnceCell::new(),
79 args_map: OnceCell::new(),
80 },
81 }
82 }
83
84 fn find_stable_config<'b>(&'a self, cfg: &'b StableConfig) -> Option<&'b ConfigMap> {
86 for rule in &cfg.rules {
87 if rule.selectors.iter().all(|s| self.selector_match(s)) {
88 return Some(&rule.configuration);
89 }
90 }
91 None
92 }
93
94 fn selector_match(&'a self, selector: &Selector) -> bool {
99 match selector.origin {
100 Origin::Language => string_selector(selector, self.process_info.language.deref()),
101 Origin::ProcessArguments => match &selector.key {
102 Some(key) => {
103 let arg_map = self.match_maps.args(self.process_info);
104 map_operator_match(selector, arg_map, key)
105 }
106 None => string_list_selector(selector, &self.process_info.args),
107 },
108 Origin::EnvironmentVariables => match &selector.key {
109 Some(key) => {
110 let env_map = self.match_maps.env(self.process_info);
111 map_operator_match(selector, env_map, key)
112 }
113 None => string_list_selector(selector, &self.process_info.envp),
114 },
115 Origin::Tags => match &selector.key {
116 Some(key) => map_operator_match(selector, self.match_maps.tags, key),
117 None => false,
118 },
119 }
120 }
121
122 fn template_config(&'a self, config_val: &str) -> anyhow::Result<String> {
131 let mut rest = config_val;
132 let mut templated = String::with_capacity(config_val.len());
133 loop {
134 let Some((head, after_bracket)) = rest.split_once("{{") else {
135 templated.push_str(rest);
136 return Ok(templated);
137 };
138 templated.push_str(head);
139 let Some((template_var, tail)) = after_bracket.split_once("}}") else {
140 anyhow::bail!("unterminated template in config")
141 };
142 let (template_var, index) = parse_template_var(template_var.trim());
143 let val = match template_var {
144 "language" => String::from_utf8_lossy(self.process_info.language.deref()),
145 "environment_variables" => {
146 template_map_key(index, self.match_maps.env(self.process_info))
147 }
148 "process_arguments" => {
149 template_map_key(index, self.match_maps.args(self.process_info))
150 }
151 "tags" => template_map_key(index, self.match_maps.tags),
152 _ => std::borrow::Cow::Borrowed("UNDEFINED"),
153 };
154 templated.push_str(&val);
155 rest = tail;
156 }
157 }
158}
159
160fn map_operator_match(selector: &Selector, map: &impl Get, key: &str) -> bool {
161 let Some(val) = map.get(key) else {
162 return false;
163 };
164 string_selector(selector, val.as_bytes())
165}
166
167fn parse_template_var(template_var: &str) -> (&str, Option<&str>) {
168 match template_var.trim().split_once('[') {
169 Some((template_var, idx)) => {
170 let Some((index, _)) = idx.split_once(']') else {
171 return (template_var, None);
172 };
173 (template_var, Some(index.trim()))
174 }
175 None => (template_var, None),
176 }
177}
178
179fn template_map_key<'a>(key: Option<&str>, map: &'a impl Get) -> Cow<'a, str> {
180 let Some(key) = key else {
181 return Cow::Borrowed("UNDEFINED");
182 };
183 Cow::Borrowed(map.get(key).unwrap_or("UNDEFINED"))
184}
185
186#[repr(C)]
187pub struct ProcessInfo {
188 pub args: Vec<Vec<u8>>,
189 pub envp: Vec<Vec<u8>>,
190 pub language: Vec<u8>,
191}
192
193fn process_envp() -> Vec<Vec<u8>> {
194 #[allow(clippy::unnecessary_filter_map)]
195 env::vars_os()
196 .filter_map(|(k, v)| {
197 #[cfg(not(unix))]
198 {
199 let mut env = Vec::new();
200 env.extend(k.to_str()?.as_bytes());
201 env.push(b'=');
202 env.extend(v.to_str()?.as_bytes());
203 Some(env)
204 }
205 #[cfg(unix)]
206 {
207 use std::os::unix::ffi::OsStrExt;
208 let mut env = Vec::new();
209 env.extend(k.as_bytes());
210 env.push(b'=');
211 env.extend(v.as_bytes());
212 Some(env)
213 }
214 })
215 .collect()
216}
217
218fn process_args() -> Vec<Vec<u8>> {
219 #[allow(clippy::unnecessary_filter_map)]
220 env::args_os()
221 .filter_map(|a| {
222 #[cfg(not(unix))]
223 {
224 Some(a.into_string().ok()?.into_bytes())
225 }
226 #[cfg(unix)]
227 {
228 use std::os::unix::ffi::OsStringExt;
229 Some(a.into_vec())
230 }
231 })
232 .collect()
233}
234
235impl ProcessInfo {
236 pub fn detect_global(language: String) -> Self {
237 let envp = process_envp();
238 let args = process_args();
239 Self {
240 args,
241 envp,
242 language: language.into_bytes(),
243 }
244 }
245}
246
247#[derive(Debug, Default, PartialEq, Eq)]
253struct ConfigMap(Box<[(String, String)]>);
254
255impl<'de> serde::Deserialize<'de> for ConfigMap {
256 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257 where
258 D: serde::Deserializer<'de>,
259 {
260 struct ConfigMapVisitor;
261 impl<'de> serde::de::Visitor<'de> for ConfigMapVisitor {
262 type Value = ConfigMap;
263
264 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
265 formatter.write_str("struct ConfigMap(HashMap<String, String>)")
266 }
267
268 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
269 where
270 A: serde::de::MapAccess<'de>,
271 {
272 let mut configs = Vec::new();
273 configs.reserve_exact(map.size_hint().unwrap_or(0));
274 loop {
275 let k = match map.next_key::<String>() {
276 Ok(Some(k)) => k,
277 Ok(None) => break,
278 Err(_) => {
279 map.next_value::<serde::de::IgnoredAny>()?;
280 continue;
281 }
282 };
283 let v = map.next_value::<String>()?;
284 configs.push((k, v));
285 }
286 Ok(ConfigMap(configs.into_boxed_slice()))
287 }
288 }
289 deserializer.deserialize_map(ConfigMapVisitor)
290 }
291}
292
293#[repr(C)]
294#[derive(Clone, Copy, serde::Deserialize, Debug, PartialEq, Eq, Hash)]
295#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
296#[allow(clippy::enum_variant_names)]
297pub enum LibraryConfigSource {
298 LocalStableConfig = 0,
301 FleetStableConfig = 1,
302}
303
304impl LibraryConfigSource {
305 pub fn to_str(&self) -> &'static str {
306 use LibraryConfigSource::*;
307 match self {
308 LocalStableConfig => "local_stable_config",
309 FleetStableConfig => "fleet_stable_config",
310 }
311 }
312}
313
314#[derive(serde::Deserialize, Debug, PartialEq, Eq)]
315#[serde(rename_all = "snake_case")]
316enum Origin {
317 ProcessArguments,
318 EnvironmentVariables,
319 Language,
320 Tags,
321}
322
323#[derive(serde::Deserialize, Debug, PartialEq, Eq)]
324#[serde(rename_all = "snake_case")]
325#[serde(tag = "operator")]
326enum Operator {
327 Exists,
328 Equals { matches: Vec<String> },
329 PrefixMatches { matches: Vec<String> },
330 SuffixMatches { matches: Vec<String> },
331 }
334
335#[derive(serde::Deserialize, Debug, PartialEq, Eq)]
336struct Selector {
337 origin: Origin,
338 #[serde(default)]
339 key: Option<String>,
340 #[serde(flatten)]
341 operator: Operator,
342}
343
344#[derive(serde::Deserialize, Debug, PartialEq, Eq)]
345struct Rule {
346 selectors: Vec<Selector>,
347 configuration: ConfigMap,
348}
349
350#[derive(serde::Deserialize, Default, Debug, PartialEq, Eq)]
351struct StableConfig {
352 #[serde(default)]
354 config_id: Option<String>,
355 #[serde(default)]
356 apm_configuration_default: ConfigMap,
357
358 #[serde(default)]
360 tags: HashMap<String, String>,
361 #[serde(default)]
362 rules: Vec<Rule>,
363}
364
365fn string_list_selector<B: Deref<Target = [u8]>>(selector: &Selector, l: &[B]) -> bool {
366 l.iter().any(|v| string_selector(selector, v.deref()))
367}
368
369fn string_selector(selector: &Selector, value: &[u8]) -> bool {
370 let matches = match &selector.operator {
371 Operator::Exists => return true,
372 Operator::Equals { matches } => matches,
373 Operator::PrefixMatches { matches } => matches,
374 Operator::SuffixMatches { matches } => matches,
375 };
376 matches
377 .iter()
378 .any(|m| string_operator_match(&selector.operator, m.as_bytes(), value))
379}
380
381fn string_operator_match(op: &Operator, matches: &[u8], value: &[u8]) -> bool {
382 match op {
383 Operator::Equals { .. } => matches == value,
384 Operator::PrefixMatches { .. } => value.starts_with(matches),
385 Operator::SuffixMatches { .. } => value.ends_with(matches),
386 Operator::Exists => true,
387 }
389}
390
391#[derive(Debug, PartialEq, Eq)]
392pub struct LibraryConfig {
395 pub name: String,
396 pub value: String,
397 pub source: LibraryConfigSource,
398 pub config_id: Option<String>,
399}
400
401#[derive(Debug)]
402struct LibraryConfigVal {
405 value: String,
406 source: LibraryConfigSource,
407 config_id: Option<String>,
408}
409
410#[derive(Debug)]
411pub struct Configurator {
412 debug_logs: bool,
413}
414
415pub enum Target {
416 Linux,
417 Macos,
418 Windows,
419}
420
421impl Target {
422 #[cfg(any(target_os = "linux", target_os = "macos", windows))]
423 const fn current() -> Self {
424 #[cfg(target_os = "linux")]
425 {
426 Self::Linux
427 }
428 #[cfg(target_os = "macos")]
429 {
430 Self::Macos
431 }
432 #[cfg(windows)]
433 {
434 Self::Windows
435 }
436 }
437}
438
439#[derive(Debug)]
440pub enum LoggedResult<T, E> {
441 Ok(T, Vec<String>),
442 Err(E),
443}
444
445impl<T, E> LoggedResult<T, E> {
446 pub fn data(self) -> Result<T, E> {
447 match self {
448 LoggedResult::Ok(value, _) => Ok(value),
449 LoggedResult::Err(err) => Err(err),
450 }
451 }
452
453 pub fn logs(&self) -> &[String] {
454 match self {
455 LoggedResult::Ok(_, logs) => logs,
456 LoggedResult::Err(_) => &[],
457 }
458 }
459
460 pub fn into_logs(self) -> Vec<String> {
461 match self {
462 LoggedResult::Ok(_, logs) => logs,
463 LoggedResult::Err(_) => Vec::new(),
464 }
465 }
466
467 pub fn logs_as_string(&self) -> String {
468 self.logs().join("\n")
469 }
470}
471
472impl Configurator {
473 #[cfg(any(target_os = "linux", target_os = "macos", windows))]
474 pub const FLEET_STABLE_CONFIGURATION_PATH: &'static str =
475 Self::fleet_stable_configuration_path(Target::current());
476
477 #[cfg(any(target_os = "linux", target_os = "macos", windows))]
478 pub const LOCAL_STABLE_CONFIGURATION_PATH: &'static str =
479 Self::local_stable_configuration_path(Target::current());
480
481 pub const fn local_stable_configuration_path(target: Target) -> &'static str {
482 match target {
483 Target::Linux => "/etc/datadog-agent/application_monitoring.yaml",
484 Target::Macos => "/opt/datadog-agent/etc/application_monitoring.yaml",
485 Target::Windows => "C:\\ProgramData\\Datadog\\application_monitoring.yaml",
486 }
487 }
488
489 pub const fn fleet_stable_configuration_path(target: Target) -> &'static str {
490 match target {
491 Target::Linux => "/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml",
492 Target::Macos => "/opt/datadog-agent/etc/stable/application_monitoring.yaml",
493 Target::Windows => "C:\\ProgramData\\Datadog\\managed\\datadog-agent\\stable\\application_monitoring.yaml",
494 }
495 }
496
497 pub fn new(debug_logs: bool) -> Self {
498 Self { debug_logs }
499 }
500
501 fn parse_stable_config_slice(&self, buf: &[u8]) -> LoggedResult<StableConfig, anyhow::Error> {
502 let stable_config = if buf.is_empty() {
503 StableConfig::default()
504 } else {
505 match serde_yaml::from_slice(buf) {
506 Ok(config) => config,
507 Err(e) => return LoggedResult::Err(e.into()),
508 }
509 };
510
511 let messages = if self.debug_logs {
512 vec![format!(
513 "Read the following static config: {stable_config:?}"
514 )]
515 } else {
516 Vec::new()
517 };
518
519 LoggedResult::Ok(stable_config, messages)
520 }
521
522 fn parse_stable_config_file<F: io::Read>(
523 &self,
524 mut f: F,
525 ) -> LoggedResult<StableConfig, anyhow::Error> {
526 let mut buffer = Vec::new();
527 match f.read_to_end(&mut buffer) {
528 Ok(_) => {}
529 Err(e) => return LoggedResult::Err(e.into()),
530 }
531 self.parse_stable_config_slice(utils::trim_bytes(&buffer))
532 }
533
534 pub fn get_config_from_file(
535 &self,
536 path_local: &Path,
537 path_managed: &Path,
538 process_info: &ProcessInfo,
539 ) -> LoggedResult<Vec<LibraryConfig>, anyhow::Error> {
540 let mut debug_messages = Vec::new();
541 if self.debug_logs {
542 debug_messages.push("Reading stable configuration from files:".to_string());
543 debug_messages.push(format!("\tlocal: {path_local:?}"));
544 debug_messages.push(format!("\tfleet: {path_managed:?}"));
545 }
546 let local_config = match fs::File::open(path_local) {
547 Ok(file) => match self.parse_stable_config_file(file) {
548 LoggedResult::Ok(config, logs) => {
549 debug_messages.extend(logs);
550 config
551 }
552 LoggedResult::Err(e) => return LoggedResult::Err(e),
553 },
554 Err(e) if e.kind() == io::ErrorKind::NotFound => StableConfig::default(),
555 Err(e) => {
556 return LoggedResult::Err(
557 anyhow::Error::from(e).context("failed to open config file"),
558 )
559 }
560 };
561 let fleet_config = match fs::File::open(path_managed) {
562 Ok(file) => match self.parse_stable_config_file(file) {
563 LoggedResult::Ok(config, logs) => {
564 debug_messages.extend(logs);
565 config
566 }
567 LoggedResult::Err(e) => return LoggedResult::Err(e),
568 },
569 Err(e) if e.kind() == io::ErrorKind::NotFound => StableConfig::default(),
570 Err(e) => {
571 return LoggedResult::Err(
572 anyhow::Error::from(e).context("failed to open config file"),
573 )
574 }
575 };
576
577 match self.get_config(local_config, fleet_config, process_info) {
578 LoggedResult::Ok(configs, msgs) => {
579 debug_messages.extend(msgs);
580 LoggedResult::Ok(configs, debug_messages)
581 }
582 LoggedResult::Err(e) => LoggedResult::Err(e),
583 }
584 }
585
586 pub fn get_config_from_bytes(
587 &self,
588 s_local: &[u8],
589 s_managed: &[u8],
590 process_info: ProcessInfo,
591 ) -> anyhow::Result<Vec<LibraryConfig>> {
592 let local_config = match self.parse_stable_config_slice(s_local) {
593 LoggedResult::Ok(config, _) => config,
594 LoggedResult::Err(e) => return Err(e),
595 };
596 let fleet_config = match self.parse_stable_config_slice(s_managed) {
597 LoggedResult::Ok(config, _) => config,
598 LoggedResult::Err(e) => return Err(e),
599 };
600
601 match self.get_config(local_config, fleet_config, &process_info) {
602 LoggedResult::Ok(configs, _) => Ok(configs),
603 LoggedResult::Err(e) => Err(e),
604 }
605 }
606
607 fn get_config(
608 &self,
609 local_config: StableConfig,
610 fleet_config: StableConfig,
611 process_info: &ProcessInfo,
612 ) -> LoggedResult<Vec<LibraryConfig>, anyhow::Error> {
613 let mut debug_messages = Vec::new();
614 if self.debug_logs {
615 debug_messages.push("\tProcess args:".to_string());
616
617 for arg in &process_info.args {
618 let arg_str = String::from_utf8_lossy(arg);
619 debug_messages.push(format!("\t\t{:?}", arg_str.as_ref()));
620 }
621
622 debug_messages.push(format!(
623 "\tProcess language: {:?}",
624 String::from_utf8_lossy(&process_info.language).as_ref()
625 ));
626 }
627
628 let mut cfg = HashMap::new();
629 match self.get_single_source_config(
631 local_config,
632 LibraryConfigSource::LocalStableConfig,
633 process_info,
634 &mut cfg,
635 ) {
636 LoggedResult::Ok(_, msgs) => debug_messages.extend(msgs),
637 LoggedResult::Err(e) => return LoggedResult::Err(e),
638 }
639
640 if self.debug_logs {
641 debug_messages.push("Called library_config_common_component:".to_string());
642 debug_messages.push(format!(
643 "\tsource: {:?}",
644 LibraryConfigSource::LocalStableConfig
645 ));
646 debug_messages.push(format!("\tconfigurator: {self:?}"));
647 }
648
649 match self.get_single_source_config(
651 fleet_config,
652 LibraryConfigSource::FleetStableConfig,
653 process_info,
654 &mut cfg,
655 ) {
656 LoggedResult::Ok(_, msgs) => debug_messages.extend(msgs),
657 LoggedResult::Err(e) => return LoggedResult::Err(e),
658 }
659
660 if self.debug_logs {
661 debug_messages.push("Called library_config_common_component:".to_string());
662 debug_messages.push(format!(
663 "\tsource: {:?}",
664 LibraryConfigSource::FleetStableConfig
665 ));
666 debug_messages.push(format!("\tconfigurator: {self:?}"));
667 }
668
669 let configs = cfg
670 .into_iter()
671 .map(|(k, v)| LibraryConfig {
672 name: k,
673 value: v.value,
674 source: v.source,
675 config_id: v.config_id,
676 })
677 .collect();
678
679 LoggedResult::Ok(configs, debug_messages)
680 }
681
682 fn get_single_source_config(
688 &self,
689 mut stable_config: StableConfig,
690 source: LibraryConfigSource,
691 process_info: &ProcessInfo,
692 cfg: &mut HashMap<String, LibraryConfigVal>,
693 ) -> LoggedResult<(), anyhow::Error> {
694 cfg.extend(
696 mem::take(&mut stable_config.apm_configuration_default)
697 .0
698 .into_vec()
700 .into_iter()
701 .map(|(k, v)| {
702 (
703 k,
704 LibraryConfigVal {
705 value: v,
706 source,
707 config_id: stable_config.config_id.clone(),
708 },
709 )
710 }),
711 );
712
713 self.get_single_source_process_config(stable_config, source, process_info, cfg)
715 }
716
717 fn get_single_source_process_config(
719 &self,
720 stable_config: StableConfig,
721 source: LibraryConfigSource,
722 process_info: &ProcessInfo,
723 library_config: &mut HashMap<String, LibraryConfigVal>,
724 ) -> LoggedResult<(), anyhow::Error> {
725 let matcher = Matcher::new(process_info, &stable_config.tags);
726 let Some(configs) = matcher.find_stable_config(&stable_config) else {
727 let messages = if self.debug_logs {
728 vec![format!("No selector matched for source {source:?}")]
729 } else {
730 Vec::new()
731 };
732 return LoggedResult::Ok((), messages);
733 };
734
735 for (name, config_val) in configs.0.iter() {
736 let value = match matcher.template_config(config_val) {
737 Ok(v) => v,
738 Err(e) => return LoggedResult::Err(e),
739 };
740 library_config.insert(
741 name.clone(),
742 LibraryConfigVal {
743 value,
744 source,
745 config_id: stable_config.config_id.clone(),
746 },
747 );
748 }
749
750 let messages = if self.debug_logs {
751 vec![format!("Will apply the following configuration:\n\tsource {source:?}\n\t{library_config:?}")]
752 } else {
753 Vec::new()
754 };
755
756 LoggedResult::Ok((), messages)
757 }
758}
759
760use utils::Get;
761mod utils {
762 use std::collections::HashMap;
763
764 pub(crate) fn trim_bytes(mut b: &[u8]) -> &[u8] {
766 while b.first().map(u8::is_ascii_whitespace).unwrap_or(false) {
767 b = &b[1..];
768 }
769 while b.last().map(u8::is_ascii_whitespace).unwrap_or(false) {
770 b = &b[..b.len() - 1];
771 }
772 b
773 }
774
775 pub(crate) trait Get {
778 fn get(&self, k: &str) -> Option<&str>;
779 }
780
781 impl Get for HashMap<&str, &str> {
782 fn get(&self, k: &str) -> Option<&str> {
783 self.get(k).copied()
784 }
785 }
786
787 impl Get for HashMap<String, String> {
788 fn get(&self, k: &str) -> Option<&str> {
789 self.get(k).map(|v| v.as_str())
790 }
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use std::{collections::HashMap, io::Write};
797
798 use super::{Configurator, LoggedResult, ProcessInfo};
799 use crate::{
800 ConfigMap, LibraryConfig, LibraryConfigSource, Matcher, Operator, Origin, Rule, Selector,
801 StableConfig,
802 };
803
804 fn test_config(local_cfg: &[u8], fleet_cfg: &[u8], expected: Vec<LibraryConfig>) {
805 let process_info: ProcessInfo = ProcessInfo {
806 args: vec![
807 b"-Djava_config_key=my_config".to_vec(),
808 b"-jar".to_vec(),
809 b"HelloWorld.jar".to_vec(),
810 ],
811 envp: vec![b"ENV=VAR".to_vec()],
812 language: b"java".to_vec(),
813 };
814 let configurator = Configurator::new(true);
815 let mut actual = configurator
816 .get_config_from_bytes(local_cfg, fleet_cfg, process_info)
817 .unwrap();
818
819 actual.sort_by_key(|c| c.name.clone());
821 assert_eq!(actual, expected);
822 }
823
824 #[test]
825 fn test_empty_configs() {
826 test_config(b"", b"", vec![]);
827 }
828
829 #[test]
830 fn test_missing_files() {
831 let configurator = Configurator::new(true);
832 let result = configurator.get_config_from_file(
833 "/file/is/missing".as_ref(),
834 "/file/is/missing_too".as_ref(),
835 &ProcessInfo {
836 args: vec![b"-jar HelloWorld.jar".to_vec()],
837 envp: vec![b"ENV=VAR".to_vec()],
838 language: b"java".to_vec(),
839 },
840 );
841 match result {
842 LoggedResult::Ok(configs, logs) => {
843 assert_eq!(configs, vec![]);
844 assert_eq!(
845 logs,
846 vec![
847 "Reading stable configuration from files:",
848 "\tlocal: \"/file/is/missing\"",
849 "\tfleet: \"/file/is/missing_too\"",
850 "\tProcess args:",
851 "\t\t\"-jar HelloWorld.jar\"",
852 "\tProcess language: \"java\"",
853 "No selector matched for source LocalStableConfig",
854 "Called library_config_common_component:",
855 "\tsource: LocalStableConfig",
856 "\tconfigurator: Configurator { debug_logs: true }",
857 "No selector matched for source FleetStableConfig",
858 "Called library_config_common_component:",
859 "\tsource: FleetStableConfig",
860 "\tconfigurator: Configurator { debug_logs: true }"
861 ]
862 );
863 }
864 LoggedResult::Err(_) => panic!("Expected success"),
865 }
866 }
867
868 #[test]
869 fn test_local_host_global_config() {
870 use LibraryConfigSource::*;
871 test_config(
872 b"
873apm_configuration_default:
874 DD_APM_TRACING_ENABLED: true
875 DD_RUNTIME_METRICS_ENABLED: true
876 DD_LOGS_INJECTION: true
877 DD_PROFILING_ENABLED: true
878 DD_DATA_STREAMS_ENABLED: true
879 DD_APPSEC_ENABLED: true
880 DD_IAST_ENABLED: true
881 DD_DYNAMIC_INSTRUMENTATION_ENABLED: true
882 DD_DATA_JOBS_ENABLED: true
883 DD_APPSEC_SCA_ENABLED: true
884 ",
885 b"",
886 vec![
887 LibraryConfig {
888 name: "DD_APM_TRACING_ENABLED".to_owned(),
889 value: "true".to_owned(),
890 source: LocalStableConfig,
891 config_id: None,
892 },
893 LibraryConfig {
894 name: "DD_APPSEC_ENABLED".to_owned(),
895 value: "true".to_owned(),
896 source: LocalStableConfig,
897 config_id: None,
898 },
899 LibraryConfig {
900 name: "DD_APPSEC_SCA_ENABLED".to_owned(),
901 value: "true".to_owned(),
902 source: LocalStableConfig,
903 config_id: None,
904 },
905 LibraryConfig {
906 name: "DD_DATA_JOBS_ENABLED".to_owned(),
907 value: "true".to_owned(),
908 source: LocalStableConfig,
909 config_id: None,
910 },
911 LibraryConfig {
912 name: "DD_DATA_STREAMS_ENABLED".to_owned(),
913 value: "true".to_owned(),
914 source: LocalStableConfig,
915 config_id: None,
916 },
917 LibraryConfig {
918 name: "DD_DYNAMIC_INSTRUMENTATION_ENABLED".to_owned(),
919 value: "true".to_owned(),
920 source: LocalStableConfig,
921 config_id: None,
922 },
923 LibraryConfig {
924 name: "DD_IAST_ENABLED".to_owned(),
925 value: "true".to_owned(),
926 source: LocalStableConfig,
927 config_id: None,
928 },
929 LibraryConfig {
930 name: "DD_LOGS_INJECTION".to_owned(),
931 value: "true".to_owned(),
932 source: LocalStableConfig,
933 config_id: None,
934 },
935 LibraryConfig {
936 name: "DD_PROFILING_ENABLED".to_owned(),
937 value: "true".to_owned(),
938 source: LocalStableConfig,
939 config_id: None,
940 },
941 LibraryConfig {
942 name: "DD_RUNTIME_METRICS_ENABLED".to_owned(),
943 value: "true".to_owned(),
944 source: LocalStableConfig,
945 config_id: None,
946 },
947 ],
948 );
949 }
950
951 #[test]
952 fn test_fleet_host_global_config() {
953 use LibraryConfigSource::*;
954 test_config(
955 b"",
956 b"
957config_id: abc
958apm_configuration_default:
959 DD_APM_TRACING_ENABLED: true
960 DD_RUNTIME_METRICS_ENABLED: true
961 DD_LOGS_INJECTION: true
962 DD_PROFILING_ENABLED: true
963 DD_DATA_STREAMS_ENABLED: true
964 DD_APPSEC_ENABLED: true
965 DD_IAST_ENABLED: true
966 DD_DYNAMIC_INSTRUMENTATION_ENABLED: true
967 FOO_BAR: quoicoubeh
968 DD_DATA_JOBS_ENABLED: true
969 DD_APPSEC_SCA_ENABLED: true
970wtf:
971- 1
972 ",
973 vec![
974 LibraryConfig {
975 name: "DD_APM_TRACING_ENABLED".to_owned(),
976 value: "true".to_owned(),
977 source: FleetStableConfig,
978 config_id: Some("abc".to_owned()),
979 },
980 LibraryConfig {
981 name: "DD_APPSEC_ENABLED".to_owned(),
982 value: "true".to_owned(),
983 source: FleetStableConfig,
984 config_id: Some("abc".to_owned()),
985 },
986 LibraryConfig {
987 name: "DD_APPSEC_SCA_ENABLED".to_owned(),
988 value: "true".to_owned(),
989 source: FleetStableConfig,
990 config_id: Some("abc".to_owned()),
991 },
992 LibraryConfig {
993 name: "DD_DATA_JOBS_ENABLED".to_owned(),
994 value: "true".to_owned(),
995 source: FleetStableConfig,
996 config_id: Some("abc".to_owned()),
997 },
998 LibraryConfig {
999 name: "DD_DATA_STREAMS_ENABLED".to_owned(),
1000 value: "true".to_owned(),
1001 source: FleetStableConfig,
1002 config_id: Some("abc".to_owned()),
1003 },
1004 LibraryConfig {
1005 name: "DD_DYNAMIC_INSTRUMENTATION_ENABLED".to_owned(),
1006 value: "true".to_owned(),
1007 source: FleetStableConfig,
1008 config_id: Some("abc".to_owned()),
1009 },
1010 LibraryConfig {
1011 name: "DD_IAST_ENABLED".to_owned(),
1012 value: "true".to_owned(),
1013 source: FleetStableConfig,
1014 config_id: Some("abc".to_owned()),
1015 },
1016 LibraryConfig {
1017 name: "DD_LOGS_INJECTION".to_owned(),
1018 value: "true".to_owned(),
1019 source: FleetStableConfig,
1020 config_id: Some("abc".to_owned()),
1021 },
1022 LibraryConfig {
1023 name: "DD_PROFILING_ENABLED".to_owned(),
1024 value: "true".to_owned(),
1025 source: FleetStableConfig,
1026 config_id: Some("abc".to_owned()),
1027 },
1028 LibraryConfig {
1029 name: "DD_RUNTIME_METRICS_ENABLED".to_owned(),
1030 value: "true".to_owned(),
1031 source: FleetStableConfig,
1032 config_id: Some("abc".to_owned()),
1033 },
1034 LibraryConfig {
1035 name: "FOO_BAR".to_owned(),
1036 value: "quoicoubeh".to_owned(),
1037 source: FleetStableConfig,
1038 config_id: Some("abc".to_owned()),
1039 },
1040 ],
1041 );
1042 }
1043
1044 #[test]
1045 fn test_merge_local_fleet() {
1046 use LibraryConfigSource::*;
1047
1048 test_config(
1049 b"
1050apm_configuration_default:
1051 DD_APM_TRACING_ENABLED: true
1052 DD_RUNTIME_METRICS_ENABLED: true
1053 DD_PROFILING_ENABLED: true
1054 ",
1055 b"
1056config_id: abc
1057apm_configuration_default:
1058 DD_APM_TRACING_ENABLED: true
1059 DD_LOGS_INJECTION: true
1060 DD_PROFILING_ENABLED: false
1061",
1062 vec![
1063 LibraryConfig {
1064 name: "DD_APM_TRACING_ENABLED".to_owned(),
1065 value: "true".to_owned(),
1066 source: FleetStableConfig,
1067 config_id: Some("abc".to_owned()),
1068 },
1069 LibraryConfig {
1070 name: "DD_LOGS_INJECTION".to_owned(),
1071 value: "true".to_owned(),
1072 source: FleetStableConfig,
1073 config_id: Some("abc".to_owned()),
1074 },
1075 LibraryConfig {
1076 name: "DD_PROFILING_ENABLED".to_owned(),
1077 value: "false".to_owned(),
1078 source: FleetStableConfig,
1079 config_id: Some("abc".to_owned()),
1080 },
1081 LibraryConfig {
1082 name: "DD_RUNTIME_METRICS_ENABLED".to_owned(),
1083 value: "true".to_owned(),
1084 source: LocalStableConfig,
1085 config_id: None,
1086 },
1087 ],
1088 );
1089 }
1090
1091 #[test]
1092 fn test_process_config() {
1093 test_config(
1094 b"
1095config_id: abc
1096tags:
1097 cluster_name: my_cluster
1098rules:
1099- selectors:
1100 - origin: language
1101 matches: [\"java\"]
1102 operator: equals
1103 - origin: process_arguments
1104 key: \"-Djava_config_key\"
1105 operator: exists
1106 - origin: process_arguments
1107 matches: [\"HelloWorld.jar\"]
1108 operator: equals
1109 configuration:
1110 DD_SERVICE: my_service_{{ tags[cluster_name] }}_{{ process_arguments[-Djava_config_key] }}_{{ language }}
1111 ",
1112 b"",
1113 vec![LibraryConfig {
1114 name: "DD_SERVICE".to_string(),
1115 value: "my_service_my_cluster_my_config_java".to_string(),
1116 source: LibraryConfigSource::LocalStableConfig,
1117 config_id: Some("abc".to_string()),
1118 }],
1119 );
1120 }
1121
1122 #[test]
1123 fn test_parse_static_config() {
1124 let mut tmp = tempfile::NamedTempFile::new().unwrap();
1125 tmp.reopen()
1126 .unwrap()
1127 .write_all(
1128 b"
1129rules:
1130- selectors:
1131 - origin: language
1132 matches: [\"java\"]
1133 operator: equals
1134 configuration:
1135 DD_PROFILING_ENABLED: true
1136 DD_SERVICE: my-service
1137 # extra keys should be skipped without errors
1138 FOOBAR: maybe??
1139",
1140 )
1141 .unwrap();
1142 let configurator = Configurator::new(true);
1143 let cfg = configurator.parse_stable_config_file(tmp.as_file_mut());
1144 let config = match cfg {
1145 LoggedResult::Ok(config, _) => config,
1146 LoggedResult::Err(_) => panic!("Expected success"),
1147 };
1148 assert_eq!(
1149 config,
1150 StableConfig {
1151 config_id: None,
1152 apm_configuration_default: ConfigMap::default(),
1153 tags: HashMap::default(),
1154 rules: vec![Rule {
1155 selectors: vec![Selector {
1156 origin: Origin::Language,
1157 operator: Operator::Equals {
1158 matches: vec!["java".to_owned()]
1159 },
1160 key: None,
1161 }],
1162 configuration: ConfigMap(
1163 vec![
1164 ("DD_PROFILING_ENABLED".to_owned(), "true".to_owned()),
1165 ("DD_SERVICE".to_owned(), "my-service".to_owned()),
1166 ("FOOBAR".to_owned(), "maybe??".to_owned()),
1167 ]
1168 .into_boxed_slice()
1169 ),
1170 }]
1171 }
1172 )
1173 }
1174
1175 #[test]
1176 fn test_selector_match() {
1177 let process_info = ProcessInfo {
1178 args: vec![b"-jar HelloWorld.jar".to_vec()],
1179 envp: vec![b"ENV=VAR".to_vec()],
1180 language: b"java".to_vec(),
1181 };
1182 let tags = HashMap::new();
1183 let matcher = Matcher::new(&process_info, &tags);
1184
1185 let test_cases = &[
1186 (
1187 Selector {
1188 key: None,
1189 origin: Origin::Language,
1190 operator: Operator::Equals {
1191 matches: vec!["java".to_owned()],
1192 },
1193 },
1194 true,
1195 ),
1196 (
1197 Selector {
1198 key: None,
1199 origin: Origin::ProcessArguments,
1200 operator: Operator::Equals {
1201 matches: vec!["-jar HelloWorld.jar".to_owned()],
1202 },
1203 },
1204 true,
1205 ),
1206 (
1207 Selector {
1208 key: None,
1209 origin: Origin::EnvironmentVariables,
1210 operator: Operator::Equals {
1211 matches: vec!["ENV=VAR".to_owned()],
1212 },
1213 },
1214 true,
1215 ),
1216 (
1217 Selector {
1218 key: None,
1219 origin: Origin::Language,
1220 operator: Operator::Equals {
1221 matches: vec!["python".to_owned()],
1222 },
1223 },
1224 false,
1225 ),
1226 ];
1227 for (i, (selector, matches)) in test_cases.iter().enumerate() {
1228 assert_eq!(matcher.selector_match(selector), *matches, "case {i}");
1229 }
1230 }
1231
1232 #[test]
1233 fn test_fleet_over_local() {
1234 let process_info: ProcessInfo = ProcessInfo {
1235 args: vec![
1236 b"-Djava_config_key=my_config".to_vec(),
1237 b"-jar".to_vec(),
1238 b"HelloWorld.jar".to_vec(),
1239 ],
1240 envp: vec![b"ENV=VAR".to_vec()],
1241 language: b"java".to_vec(),
1242 };
1243 let configurator = Configurator::new(true);
1244 let config = configurator
1245 .get_config_from_bytes(
1246 b"
1247config_id: abc
1248tags:
1249 cluster_name: my_cluster
1250rules:
1251- selectors:
1252 - origin: language
1253 matches: [\"java\"]
1254 operator: equals
1255 configuration:
1256 DD_SERVICE: local
1257",
1258 b"
1259config_id: def
1260rules:
1261- selectors:
1262 - origin: language
1263 matches: [\"java\"]
1264 operator: equals
1265 configuration:
1266 DD_SERVICE: managed",
1267 process_info,
1268 )
1269 .unwrap();
1270 assert_eq!(
1271 config,
1272 vec![LibraryConfig {
1273 name: "DD_SERVICE".to_string(),
1274 value: "managed".to_string(),
1275 source: LibraryConfigSource::FleetStableConfig,
1276 config_id: Some("def".to_string()),
1277 }]
1278 );
1279 }
1280}