saluki_components/config_registry/mod.rs
1//! Configuration key registry.
2//!
3//! A programmatic registry of all recognized configuration keys. Each entry describes the key
4//! purely from the configuration system's perspective: its canonical YAML path, the environment
5//! variables that map to it, the shape of its value, and which internal config structs consume it.
6//!
7//! This registry is intentionally free of Rust field names and struct internals—it models the
8//! configuration surface as an operator would see it, and can be used at runtime to detect
9//! unknown or unsupported keys in a loaded configuration file.
10//!
11//! ## User Guide
12//!
13//! The config registry tests enforce uniqueness and completeness constraints similar to
14//! primary-key and unique constraints in a relational database. Every key in the vendored
15//! schema (`core_schema.yaml`) must appear in exactly one of two places: an annotation
16//! ([`SalukiAnnotation`]) or the ignored-keys list (`etc/ignored_keys.yaml`). No key may
17//! appear in both, and no key may appear twice within either list.
18//!
19//! If a test fails, it means one of these constraints was violated. Common scenarios:
20//!
21//! ### Adding a Configuration Key
22//!
23//! When Saluki gains support for a new key, add a [`SalukiAnnotation`] in the appropriate
24//! `config_registry::datadog::*` submodule with [`SupportLevel::Full`] or
25//! [`SupportLevel::Partial`] and a non-empty `used_by` list. If the key was previously in
26//! `etc/ignored_keys.yaml`, remove it from there. If it was previously
27//! [`SupportLevel::Incompatible`], move it from `datadog/unsupported.rs` to the
28//! appropriate submodule and update its support level.
29//!
30//! ### Updating the Vendored Schema
31//!
32//! After updating `vendor/core_schema.yaml`, rebuild to regenerate the schema module
33//! (the `build.rs` script handles this automatically). Any new keys will cause the
34//! `all_schema_entries_are_annotated_or_ignored` test to fail. For each new key, decide:
35//!
36//! - **Irrelevant to ADP**: add to `etc/ignored_keys.yaml` with a reason.
37//! - **Incompatible**: add to `datadog/unsupported.rs` with a [`Severity`] level.
38//! - **Supported**: add to the appropriate `datadog/*.rs` submodule.
39//!
40//! If the update removed keys, the `no_stale_entries` test catches annotations or
41//! ignored entries that reference keys no longer in the schema. Delete the stale entries.
42
43mod classifier;
44pub mod datadog;
45/// Generated schema entries from the vendored Datadog Agent config schema.
46pub mod generated;
47
48pub use classifier::{Classification, ConfigClassifier};
49
50pub(crate) use self::datadog::ALL_ANNOTATIONS;
51#[cfg(any(test, feature = "config-test-support"))]
52pub(crate) use self::datadog::SUPPORTED_ANNOTATIONS;
53#[cfg(test)]
54pub(crate) use self::generated::schema::ALL_SCHEMA_ENTRIES;
55#[cfg(test)]
56pub(crate) use self::generated::schema::IGNORED_ENTRIES;
57
58/// Declares a set of [`SalukiAnnotation`] constants and generates a companion `ALL` slice.
59///
60/// Each entry declares one named `pub const` annotation. The macro also emits:
61///
62/// ```ignore
63/// pub const ALL: &[&SalukiAnnotation] = &[&NAME1, &NAME2, ...];
64/// ```
65///
66/// so that `datadog/mod.rs` can aggregate submodules with a single
67/// `v.extend_from_slice(my_module::ALL)` line rather than listing every constant by name.
68///
69/// # Example
70///
71/// ```ignore
72/// declare_annotations! {
73/// /// Doc comment for PROXY_HTTP.
74/// PROXY_HTTP = SalukiAnnotation { ... };
75/// PROXY_HTTPS = SalukiAnnotation { ... };
76/// }
77/// ```
78#[macro_export]
79macro_rules! declare_annotations {
80 ( $( $(#[$attr:meta])* $name:ident = $val:expr ;)+ ) => {
81 $(
82 $(#[$attr])*
83 pub const $name: $crate::config_registry::SalukiAnnotation = $val;
84 )+
85
86 /// All annotations declared in this module, in declaration order.
87 pub const ALL: &[$crate::config_registry::SalukiAnnotationRef] = &[
88 $( &$name, )+
89 ];
90 };
91}
92
93/// Shared helpers for config smoke tests.
94#[cfg(any(test, feature = "config-test-support"))]
95pub mod test_support;
96
97/// Identifiers for known configuration structs.
98///
99/// Used as values in [`SalukiAnnotation::used_by`] to declare which structs consume a given key.
100/// Adding a new struct here is the first step when registering its configuration keys.
101pub mod structs {
102 /// Identifier for `ProxyConfiguration`.
103 pub const PROXY_CONFIGURATION: &str = "ProxyConfiguration";
104 /// Identifier for `ForwarderConfiguration`.
105 pub const FORWARDER_CONFIGURATION: &str = "ForwarderConfiguration";
106 /// Identifier for `DogStatsDConfiguration`.
107 pub const DOGSTATSD_CONFIGURATION: &str = "DogStatsDConfiguration";
108 /// Identifier for `ContainerdConfiguration`.
109 pub const CONTAINERD_CONFIGURATION: &str = "ContainerdConfiguration";
110 /// Identifier for `OtlpConfiguration`.
111 pub const OTLP_CONFIGURATION: &str = "OtlpConfiguration";
112 /// Identifier for `AggregateConfiguration`.
113 pub const AGGREGATE_CONFIGURATION: &str = "AggregateConfiguration";
114 /// Identifier for `DogStatsDMapperConfiguration`.
115 pub const DOGSTATSD_MAPPER_CONFIGURATION: &str = "DogStatsDMapperConfiguration";
116 /// Identifier for `DogStatsDDebugLogConfiguration`.
117 pub const DOGSTATSD_DEBUG_LOG_CONFIGURATION: &str = "DogStatsDDebugLogConfiguration";
118 /// Identifier for `DogStatsDPrefixFilterConfiguration`.
119 pub const DOGSTATSD_PREFIX_FILTER_CONFIGURATION: &str = "DogStatsDPrefixFilterConfiguration";
120 /// Identifier for `DatadogMetricsConfiguration`.
121 pub const DATADOG_METRICS_CONFIGURATION: &str = "DatadogMetricsConfiguration";
122 /// Identifier for `DatadogTraceConfiguration`.
123 pub const DATADOG_TRACE_CONFIGURATION: &str = "DatadogTraceConfiguration";
124 /// Identifier for `DatadogLogsConfiguration`.
125 pub const DATADOG_LOGS_CONFIGURATION: &str = "DatadogLogsConfiguration";
126 /// Identifier for `DatadogEventsConfiguration`.
127 pub const DATADOG_EVENTS_CONFIGURATION: &str = "DatadogEventsConfiguration";
128 /// Identifier for `DatadogServiceChecksConfiguration`.
129 pub const DATADOG_SERVICE_CHECKS_CONFIGURATION: &str = "DatadogServiceChecksConfiguration";
130 /// Identifier for `DatadogApmStatsEncoderConfiguration`.
131 pub const DATADOG_APM_STATS_ENCODER_CONFIGURATION: &str = "DatadogApmStatsEncoderConfiguration";
132 /// Identifier for `OtlpDecoderConfiguration`.
133 pub const OTLP_DECODER_CONFIGURATION: &str = "OtlpDecoderConfiguration";
134 /// Identifier for `OtlpRelayConfiguration`.
135 pub const OTLP_RELAY_CONFIGURATION: &str = "OtlpRelayConfiguration";
136 /// Identifier for `TraceObfuscationConfiguration`.
137 pub const TRACE_OBFUSCATION_CONFIGURATION: &str = "TraceObfuscationConfiguration";
138 /// Identifier for `RemoteAgentClientConfiguration`.
139 pub const REMOTE_AGENT_CLIENT_CONFIGURATION: &str = "RemoteAgentClientConfiguration";
140 /// Keys read via `get_typed` / `try_get_typed` rather than struct deserialization.
141 pub const GET_TYPED: &str = "get_typed";
142}
143
144/// The ADP pipeline a config key affects.
145///
146/// Primary user-controlled toggles are represented.
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
148pub enum Pipeline {
149 /// DogStatsD metrics pipeline.
150 DogStatsD,
151 /// Agent checks pipeline.
152 Checks,
153 /// OTLP ingestion frontend.
154 Otlp,
155 /// Internal trace processing. Active when OTLP is enabled and proxy/relay mode (which uses the
156 /// core Agent for transport) is off.
157 Traces,
158}
159
160/// Which pipelines a [`SalukiAnnotation`] affects.
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub enum PipelineAffinity {
163 /// The list of pipelines affected by the `SalukiAnnotation`.
164 ///
165 /// This list must be non-empty, enforced by test.
166 Pipelines(&'static [Pipeline]),
167 /// The `SalukiAnnotation` affects all pipelines or ADP behavior as a whole.
168 CrossCutting,
169}
170
171/// The `Severity` level of a config key that Saluki doesn't support.
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum Severity {
174 /// Saluki's incompatibility with the key is considered minor.
175 Low,
176
177 /// Saluki's incompatibility with the key is considered potentially impactful.
178 Medium,
179
180 /// Saluki's incompatibility with the key is considered problematic.
181 High,
182}
183
184/// How well saluki supports a given configuration key.
185///
186/// Used in [`SalukiAnnotation`] to classify each key from saluki's perspective. `Ignored` is
187/// reserved for keys in the schema that have no annotation at all—it must not appear in any
188/// handwritten annotation.
189///
190/// Invariants enforced at test time:
191/// - `Full` and `Partial` require a non-empty `used_by` list.
192/// - `Incompatible` requires an empty `used_by` list.
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
194pub enum SupportLevel {
195 /// Fully supported. The key is wired up end-to-end and must be consumed by at least one struct.
196 Full,
197 /// Partially supported. The key is consumed by at least one struct, but support is incomplete.
198 Partial,
199 /// Explicitly incompatible. Saluki doesn't support this key and may not behave as expected in
200 /// its presence; `used_by` must be empty. Support for the key may be added in the future but
201 /// tracking such intent isn't encoded here.
202 Incompatible(Severity),
203 /// Intentionally ignored. The key isn't relevant to Saluki. This assignment must be
204 /// intentionally chosen and specified for a key. We never assume that a key is `Ignored` just
205 /// because we're unaware of it.
206 #[allow(unused)]
207 Ignored,
208 /// Unrecognized. The Saluki codebase is unaware of the existence of this key. It's not in our
209 /// vendored datadog config schema, nor is it annotated.
210 #[allow(unused)]
211 Unrecognized,
212}
213
214/// The shape of a configuration value.
215///
216/// Describes how a value should be parsed from both YAML and environment variables.
217/// `StringList` values are represented as a YAML sequence or a space-separated string in env vars.
218#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219pub enum ValueType {
220 /// A boolean (`true` / `false`).
221 Bool,
222 /// A UTF-8 string.
223 String,
224 /// An unsigned integer.
225 Integer,
226 /// A floating-point number.
227 Float,
228 /// A list of strings (YAML sequence or space-separated env var string).
229 StringList,
230}
231
232/// Which schema source of truth defined the `SchemaEntry`
233#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
234#[repr(u8)]
235pub(crate) enum Schema {
236 /// Saluki defined the `SchemaEntry` and the key isn't expected to exist in the vendored Datadog config schema.
237 Saluki,
238 /// The vendored Datadog config schema defines the `SchemaEntry`.
239 Datadog,
240}
241
242/// Schema-derived metadata for a single configuration key.
243///
244/// Generated from the vendored Datadog Agent config schema. Contains only what the schema
245/// knows: the canonical YAML path, declared environment variables, and value type. Saluki-specific
246/// fields (`used_by`, etc.) live in [`SalukiAnnotation`] instead.
247///
248/// Don't construct these manually—they're produced by `cargo xtask gen-config-schema` and
249/// live in `config_registry::generated::schema`.
250#[derive(Debug)]
251pub struct SchemaEntry {
252 /// The source of truth from which this entry was derived.
253 #[allow(dead_code)]
254 pub(crate) schema: Schema,
255
256 /// Canonical dot-separated YAML path for this key (for example, `"proxy.http"`).
257 pub yaml_path: &'static str,
258
259 /// Environment variables that deliver this value, as declared in the schema.
260 ///
261 /// Empty when the schema marks the key `no-env` or lists no env vars. Annotations may
262 /// override this with additional or corrected env vars.
263 pub env_vars: &'static [&'static str],
264
265 /// Shape of the value.
266 pub value_type: ValueType,
267
268 /// JSON-encoded default value from the Agent schema, if present.
269 ///
270 /// Examples: `Some("true")`, `Some("8125")`, `Some("\"datadoghq.com\"")`, `None`.
271 /// Used by smoke tests to auto-detect when a generic test value matches the default
272 /// and flip it so the test actually exercises the field.
273 pub default: Option<&'static str>,
274}
275
276/// Saluki-specific annotation for a single configuration key.
277///
278/// Pairs a [`SchemaEntry`] (generated from the vendored schema) with the metadata that only
279/// saluki knows: which internal config structs consume the key, and any corrections to the
280/// schema's env var list.
281///
282/// These are hand-written constants, one per key saluki cares about, and live in
283/// `config_registry::datadog::*` submodules. They're never overwritten by codegen.
284#[derive(Debug)]
285pub struct SalukiAnnotation {
286 /// The schema entry this annotation enriches.
287 pub schema: &'static SchemaEntry,
288
289 /// How well saluki supports this key.
290 ///
291 /// Must not be [`SupportLevel::Ignored`] or [`SupportLevel::Unrecognized`] which are reserved
292 /// for unannotated keys.
293 pub support_level: SupportLevel,
294
295 /// Additional YAML paths beyond the canonical one in the schema (aliases).
296 ///
297 /// Most keys have no aliases; leave this as `&[]` unless the config system recognises
298 /// the key under more than one dot-separated path.
299 pub additional_yaml_paths: &'static [&'static str],
300
301 /// Overrides the schema's `env_vars` list entirely when `Some`.
302 ///
303 /// Use when the schema marks a key `no-env` but env vars are actually supported, or when
304 /// the schema's list is incorrect or incomplete (for example, the proxy sub-keys).
305 pub env_var_override: Option<&'static [&'static str]>,
306
307 /// Config structs that incorporate this key, as [`structs`] constants.
308 ///
309 /// Must be non-empty for [`SupportLevel::Full`] and [`SupportLevel::Partial`].
310 /// Must be empty for [`SupportLevel::Incompatible`].
311 pub used_by: &'static [&'static str],
312
313 /// Overrides the schema's `value_type` when `Some`.
314 ///
315 /// Use when the schema declares a key as `Float` but the consuming Rust field is `i32` (or
316 /// similarly mismatched), so that smoke tests inject a value the struct can actually parse.
317 pub value_type_override: Option<ValueType>,
318
319 /// Overrides the smoke-test injected value entirely when `Some`.
320 ///
321 /// Must be a valid JSON literal (for example, `Some("[{\"name\":\"test\"}]")`). Use when the field
322 /// requires a structured value that the generic `ValueType`-derived test values can't satisfy
323 /// (for example, JSON-encoded arrays or objects).
324 pub test_json: Option<&'static str>,
325
326 /// Which pipelines this key affects.
327 pub pipeline_affinity: PipelineAffinity,
328}
329
330/// A reference to a [`SalukiAnnotation`], used as the element type of `ALL` slices generated by
331/// [`declare_annotations!`].
332pub type SalukiAnnotationRef = &'static SalukiAnnotation;
333
334impl SalukiAnnotation {
335 /// The canonical YAML path for this key (from the schema).
336 pub fn yaml_path(&self) -> &'static str {
337 self.schema.yaml_path
338 }
339
340 /// All YAML paths for this key: canonical first, then any aliases.
341 pub fn all_yaml_paths(&self) -> impl Iterator<Item = &'static str> {
342 std::iter::once(self.schema.yaml_path).chain(self.additional_yaml_paths.iter().copied())
343 }
344
345 /// Effective env vars: the override list if set, otherwise the schema's list.
346 pub fn effective_env_vars(&self) -> &'static [&'static str] {
347 self.env_var_override.unwrap_or(self.schema.env_vars)
348 }
349
350 /// Shape of the value: override if set, otherwise from the schema.
351 pub fn value_type(&self) -> ValueType {
352 self.value_type_override.unwrap_or(self.schema.value_type)
353 }
354}
355
356/// A fully resolved configuration key, derived from a [`SalukiAnnotation`] at registry init time.
357///
358/// Used for runtime unknown-key detection and anywhere a flattened, owned view of a key is
359/// needed. For test infrastructure, prefer working with [`SalukiAnnotation`] directly.
360#[derive(Debug)]
361pub struct ConfigKey {
362 /// All dot-separated YAML paths that deliver this value.
363 pub yaml_paths: Vec<&'static str>,
364
365 /// All environment variables that deliver this value.
366 pub env_vars: Vec<&'static str>,
367
368 /// Shape of the value.
369 pub value_type: ValueType,
370
371 /// Config structs that incorporate this key, as [`structs`] constants.
372 pub used_by: &'static [&'static str],
373}
374
375impl From<&SalukiAnnotation> for ConfigKey {
376 fn from(a: &SalukiAnnotation) -> Self {
377 ConfigKey {
378 yaml_paths: a.all_yaml_paths().collect(),
379 env_vars: a.effective_env_vars().to_vec(),
380 value_type: a.value_type(),
381 used_by: a.used_by,
382 }
383 }
384}