Skip to main content

saluki_components/config_registry/datadog/
mod.rs

1//! Datadog Agent configuration registry entries.
2
3pub mod aggregate;
4pub mod containerd;
5pub mod dogstatsd;
6pub mod dogstatsd_mapper;
7pub mod dogstatsd_prefix_filter;
8pub mod encoders;
9pub mod forwarder;
10pub mod get_typed;
11pub mod otlp;
12pub mod proxy;
13pub mod trace_obfuscation;
14pub(super) mod unsupported;
15
16use std::sync::LazyLock;
17
18use super::SalukiAnnotation;
19
20/// Supported saluki annotations across every sub-system, in registration order.
21///
22/// The source of truth for which config keys saluki consumes partially or fully supports.
23/// Used by the smoke test runner and runtime unknown-key detection.
24pub(crate) static SUPPORTED_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
25    let mut v = Vec::new();
26    v.extend_from_slice(aggregate::ALL);
27    v.extend_from_slice(containerd::ALL);
28    v.extend_from_slice(dogstatsd::ALL);
29    v.extend_from_slice(dogstatsd_mapper::ALL);
30    v.extend_from_slice(forwarder::ALL);
31    v.extend_from_slice(get_typed::ALL);
32    v.extend_from_slice(dogstatsd_prefix_filter::ALL);
33    v.extend_from_slice(encoders::ALL);
34    v.extend_from_slice(otlp::ALL);
35    v.extend_from_slice(proxy::ALL);
36    v.extend_from_slice(trace_obfuscation::ALL);
37    v
38});
39
40/// Annotations for keys that Saluki intentionally doesn't support.
41///
42/// All entries have [`Incompatible`](super::SupportLevel::Incompatible) and empty `used_by`.
43pub(crate) static UNSUPPORTED_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
44    let mut v = Vec::new();
45    v.extend_from_slice(unsupported::ALL);
46    v
47});
48
49/// All saluki annotations: supported and unsupported combined.
50///
51/// Used by the adp-runtime config check to classify every key in the resolved config.
52pub(crate) static ALL_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
53    let mut v = SUPPORTED_ANNOTATIONS.clone();
54    v.extend_from_slice(&UNSUPPORTED_ANNOTATIONS);
55    v
56});
57
58#[cfg(test)]
59mod registry_tests {
60    use std::collections::HashSet;
61
62    use super::*;
63    use crate::config_registry::{PipelineAffinity, Schema, SupportLevel, ALL_SCHEMA_ENTRIES, IGNORED_ENTRIES};
64
65    fn check_for_duplicates(it: impl Iterator<Item = impl AsRef<str>>) -> Result<(), String> {
66        let mut seen = std::collections::HashSet::new();
67        let mut duplicates = Vec::new();
68        for item in it {
69            let s = item.as_ref().to_owned();
70            if !seen.insert(s.clone()) {
71                duplicates.push(s);
72            }
73        }
74
75        if duplicates.is_empty() {
76            Ok(())
77        } else {
78            Err(format!("\n{}", duplicates.join("\n")))
79        }
80    }
81
82    #[test]
83    fn pipelines_affinity_slice_is_non_empty() {
84        for annotation in ALL_ANNOTATIONS.iter() {
85            if let PipelineAffinity::Pipelines(ps) = annotation.pipeline_affinity {
86                assert!(
87                    !ps.is_empty(),
88                    "annotation '{}' has PipelineAffinity::Pipelines(&[]) - \
89                     At least one affected Pipeline is required. If all pipelines are affected, \
90                     use Pipeline::CrossCutting.",
91                    annotation.yaml_path(),
92                );
93            }
94        }
95    }
96
97    #[test]
98    fn annotation_invariants() {
99        for annotation in SUPPORTED_ANNOTATIONS.iter() {
100            let path = annotation.yaml_path();
101            match annotation.support_level {
102                SupportLevel::Full | SupportLevel::Partial => {
103                    assert!(
104                        !annotation.used_by.is_empty(),
105                        "annotation '{}' has support level {:?} but used_by is empty — \
106                         add the consuming struct name(s) to used_by, or change the support level. \
107                         See config_registry/mod.rs module docs for details.",
108                        path,
109                        annotation.support_level,
110                    );
111                }
112                SupportLevel::Incompatible(_) => {
113                    assert!(
114                        annotation.used_by.is_empty(),
115                        "annotation '{}' has support level Incompatible but used_by is not empty — \
116                         remove the struct name(s) from used_by, or change the support level. \
117                         See config_registry/mod.rs module docs for details.",
118                        path,
119                    );
120                }
121                SupportLevel::Ignored | SupportLevel::Unrecognized => {
122                    panic!(
123                        "annotation '{}' has support level {:?} — \
124                         Ignored and Unrecognized are reserved for unannotated keys and must not \
125                         appear in a handwritten SalukiAnnotation. \
126                         See config_registry/mod.rs module docs for details.",
127                        path, annotation.support_level
128                    );
129                }
130            }
131        }
132    }
133
134    /// This test ensures that the same config key doesn't appear in both the supported and unsupported lists of
135    /// `SalukiAnnotations`.
136    #[test]
137    fn no_overlap_between_supported_and_unsupported() {
138        let supported_paths: HashSet<&str> = SUPPORTED_ANNOTATIONS.iter().map(|a| a.yaml_path()).collect();
139
140        let duplicates: Vec<&str> = UNSUPPORTED_ANNOTATIONS
141            .iter()
142            .map(|a| a.yaml_path())
143            .filter(|p| supported_paths.contains(p))
144            .collect();
145
146        if !duplicates.is_empty() {
147            panic!(
148                "{} key(s) appear in both SUPPORTED_ANNOTATIONS and UNSUPPORTED_ANNOTATIONS:\n{}\n\
149                 See config_registry/mod.rs module docs for details.",
150                duplicates.len(),
151                duplicates
152                    .iter()
153                    .map(|p| format!("  - {}", p))
154                    .collect::<Vec<_>>()
155                    .join("\n"),
156            );
157        }
158    }
159
160    #[test]
161    fn no_duplicates_in_supported_annotations() {
162        if let Err(dupes) = check_for_duplicates(SUPPORTED_ANNOTATIONS.iter().map(|&a| a.yaml_path())) {
163            panic!("Duplicates in SUPPORTED_ANNOTATIONS:{dupes}\nSee config_registry/mod.rs module docs for details.");
164        }
165    }
166
167    #[test]
168    fn no_duplicates_in_unsupported_annotations() {
169        if let Err(dupes) = check_for_duplicates(UNSUPPORTED_ANNOTATIONS.iter().map(|&a| a.yaml_path())) {
170            panic!(
171                "Duplicates in UNSUPPORTED_ANNOTATIONS:{dupes}\nSee config_registry/mod.rs module docs for details."
172            );
173        }
174    }
175
176    #[test]
177    fn no_duplicates_in_ignored_keys() {
178        if let Err(dupes) = check_for_duplicates(IGNORED_ENTRIES.iter()) {
179            panic!("Duplicates in IGNORED_ENTRIES:{dupes}\nSee config_registry/mod.rs module docs for details.");
180        }
181    }
182
183    #[test]
184    fn no_overlap_between_annotations_and_ignored() {
185        let annotation_paths: HashSet<&str> = ALL_ANNOTATIONS.iter().map(|a| a.yaml_path()).collect();
186        let overlaps: Vec<&&str> = IGNORED_ENTRIES
187            .iter()
188            .filter(|k| annotation_paths.contains(**k))
189            .collect();
190        if !overlaps.is_empty() {
191            panic!(
192                "{} key(s) appear in both ALL_ANNOTATIONS and IGNORED_ENTRIES:\n{}\n\
193                 See config_registry/mod.rs module docs for details.",
194                overlaps.len(),
195                overlaps
196                    .iter()
197                    .map(|p| format!("  - {}", p))
198                    .collect::<Vec<_>>()
199                    .join("\n"),
200            );
201        }
202    }
203
204    #[test]
205    fn no_stale_entries() {
206        let schema_paths: HashSet<&str> = ALL_SCHEMA_ENTRIES.iter().map(|e| e.yaml_path).collect();
207
208        let stale_annotations: Vec<&str> = ALL_ANNOTATIONS
209            .iter()
210            .filter(|a| a.schema.schema == Schema::Datadog)
211            .map(|a| a.yaml_path())
212            .filter(|p| !schema_paths.contains(p))
213            .collect();
214
215        let stale_ignored: Vec<&&str> = IGNORED_ENTRIES.iter().filter(|k| !schema_paths.contains(**k)).collect();
216
217        if stale_annotations.is_empty() && stale_ignored.is_empty() {
218            return;
219        }
220
221        let mut msg = String::new();
222        if !stale_annotations.is_empty() {
223            msg.push_str(&format!(
224                "{} stale annotation(s) not in schema:\n{}\n",
225                stale_annotations.len(),
226                stale_annotations
227                    .iter()
228                    .map(|p| format!("  - {}", p))
229                    .collect::<Vec<_>>()
230                    .join("\n"),
231            ));
232        }
233        if !stale_ignored.is_empty() {
234            msg.push_str(&format!(
235                "{} stale ignored key(s) not in schema:\n{}",
236                stale_ignored.len(),
237                stale_ignored
238                    .iter()
239                    .map(|p| format!("  - {}", p))
240                    .collect::<Vec<_>>()
241                    .join("\n"),
242            ));
243        }
244        panic!("{msg}\nSee config_registry/mod.rs module docs for details.");
245    }
246
247    #[test]
248    fn saluki_schema_entries_not_in_vendored_schema() {
249        let schema_paths: HashSet<&str> = ALL_SCHEMA_ENTRIES.iter().map(|e| e.yaml_path).collect();
250
251        let misclassified: Vec<&str> = ALL_ANNOTATIONS
252            .iter()
253            .filter(|a| a.schema.schema == Schema::Saluki)
254            .map(|a| a.yaml_path())
255            .filter(|p| schema_paths.contains(p))
256            .collect();
257
258        if !misclassified.is_empty() {
259            panic!(
260                "{} annotation(s) marked Schema::Saluki but found in ALL_SCHEMA_ENTRIES \
261                 (should reference the generated schema:: constant instead):\n{}\n\
262                 See config_registry/mod.rs module docs for details.",
263                misclassified.len(),
264                misclassified
265                    .iter()
266                    .map(|p| format!("  - {}", p))
267                    .collect::<Vec<_>>()
268                    .join("\n"),
269            );
270        }
271    }
272
273    #[test]
274    fn all_schema_entries_are_annotated_or_ignored() {
275        // The union of all annotated keys plus all the keys we intend to ignore.
276        let all_accounted_for_entries: HashSet<&str> = HashSet::from_iter(
277            ALL_ANNOTATIONS
278                .iter()
279                .map(|&annotation| annotation.yaml_path())
280                .chain(IGNORED_ENTRIES.iter().copied()),
281        );
282
283        let mut missing_keys = Vec::new();
284        for schema_key in ALL_SCHEMA_ENTRIES.iter().map(|&entry| entry.yaml_path) {
285            if !all_accounted_for_entries.contains(schema_key) {
286                missing_keys.push(schema_key);
287            }
288        }
289
290        if !missing_keys.is_empty() {
291            panic!(
292                "{} config key(s) are missing from the Saluki registry: \n\n{}\n\n\
293                 See config_registry/mod.rs module docs for details.",
294                missing_keys.len(),
295                missing_keys.join("\n")
296            );
297        }
298    }
299}