vector/config/loading/
config_builder.rs1use std::{collections::HashMap, io::Read};
2
3use indexmap::IndexMap;
4use toml::value::Table;
5
6use super::{ComponentHint, Process, deserialize_table, loader, prepare_input, secret};
7use crate::config::{
8 ComponentKey, ConfigBuilder, EnrichmentTableOuter, SinkOuter, SourceOuter, TestDefinition,
9 TransformOuter,
10};
11
12#[derive(Debug)]
13pub struct ConfigBuilderLoader {
14 builder: ConfigBuilder,
15 secrets: HashMap<String, String>,
16 interpolate_env: bool,
17}
18
19impl ConfigBuilderLoader {
20 pub const fn interpolate_env(mut self, interpolate: bool) -> Self {
22 self.interpolate_env = interpolate;
23 self
24 }
25
26 pub fn secrets(mut self, secrets: HashMap<String, String>) -> Self {
28 self.secrets = secrets;
29 self
30 }
31
32 pub const fn allow_empty(mut self, allow_empty: bool) -> Self {
34 self.builder.allow_empty = allow_empty;
35 self
36 }
37
38 pub fn load_from_paths(
40 self,
41 config_paths: &[super::ConfigPath],
42 ) -> Result<ConfigBuilder, Vec<String>> {
43 super::loader_from_paths(self, config_paths)
44 }
45
46 pub fn load_from_input<R: Read>(
48 self,
49 input: R,
50 format: super::Format,
51 ) -> Result<ConfigBuilder, Vec<String>> {
52 super::loader_from_input(self, input, format)
53 }
54}
55
56impl Default for ConfigBuilderLoader {
57 fn default() -> Self {
58 Self {
59 builder: ConfigBuilder::default(),
60 secrets: HashMap::new(),
61 interpolate_env: super::env_var_interpolation_enabled(),
62 }
63 }
64}
65
66impl Process for ConfigBuilderLoader {
67 fn prepare<R: Read>(&mut self, input: R) -> Result<String, Vec<String>> {
69 let prepared_input = prepare_input(input, self.interpolate_env)?;
70 Ok(if self.secrets.is_empty() {
71 prepared_input
72 } else {
73 secret::interpolate(&prepared_input, &self.secrets)?
74 })
75 }
76
77 fn merge(&mut self, table: Table, hint: Option<ComponentHint>) -> Result<(), Vec<String>> {
79 match hint {
80 Some(ComponentHint::Source) => {
81 self.builder.sources.extend(deserialize_table::<
82 IndexMap<ComponentKey, SourceOuter>,
83 >(table)?);
84 }
85 Some(ComponentHint::Sink) => {
86 self.builder.sinks.extend(
87 deserialize_table::<IndexMap<ComponentKey, SinkOuter<_>>>(table)?,
88 );
89 }
90 Some(ComponentHint::Transform) => {
91 self.builder.transforms.extend(deserialize_table::<
92 IndexMap<ComponentKey, TransformOuter<_>>,
93 >(table)?);
94 }
95 Some(ComponentHint::EnrichmentTable) => {
96 self.builder.enrichment_tables.extend(deserialize_table::<
97 IndexMap<ComponentKey, EnrichmentTableOuter<_>>,
98 >(table)?);
99 }
100 Some(ComponentHint::Test) => {
101 self.builder.tests.extend(
104 deserialize_table::<IndexMap<String, TestDefinition<String>>>(table)?
105 .into_iter()
106 .map(|(_, test)| test),
107 );
108 }
109 None => {
110 self.builder.append(deserialize_table(table)?)?;
111 }
112 };
113
114 Ok(())
115 }
116}
117
118impl loader::Loader<ConfigBuilder> for ConfigBuilderLoader {
119 fn take(self) -> ConfigBuilder {
121 self.builder
122 }
123}
124
125#[cfg(all(
126 test,
127 feature = "sinks-elasticsearch",
128 feature = "transforms-sample",
129 feature = "sources-demo_logs",
130 feature = "sinks-console"
131))]
132mod tests {
133 use std::path::PathBuf;
134
135 use super::ConfigBuilderLoader;
136 use crate::config::{ComponentKey, ConfigPath};
137
138 #[test]
139 fn load_namespacing_folder() {
140 let path = PathBuf::from(".")
141 .join("tests")
142 .join("namespacing")
143 .join("success");
144 let configs = vec![ConfigPath::Dir(path)];
145 let builder = ConfigBuilderLoader::default()
146 .interpolate_env(true)
147 .load_from_paths(&configs)
148 .unwrap();
149 assert!(
150 builder
151 .transforms
152 .contains_key(&ComponentKey::from("apache_parser"))
153 );
154 assert!(
155 builder
156 .sources
157 .contains_key(&ComponentKey::from("apache_logs"))
158 );
159 assert!(
160 builder
161 .sinks
162 .contains_key(&ComponentKey::from("es_cluster"))
163 );
164 assert_eq!(builder.tests.len(), 2);
165 }
166
167 #[test]
168 fn load_namespacing_ignore_invalid() {
169 let path = PathBuf::from(".")
170 .join("tests")
171 .join("namespacing")
172 .join("ignore-invalid");
173 let configs = vec![ConfigPath::Dir(path)];
174 ConfigBuilderLoader::default()
175 .interpolate_env(true)
176 .load_from_paths(&configs)
177 .unwrap();
178 }
179
180 #[test]
181 fn load_directory_ignores_unknown_file_formats() {
182 let path = PathBuf::from(".")
183 .join("tests")
184 .join("config-dir")
185 .join("ignore-unknown");
186 let configs = vec![ConfigPath::Dir(path)];
187 ConfigBuilderLoader::default()
188 .interpolate_env(true)
189 .load_from_paths(&configs)
190 .unwrap();
191 }
192
193 #[test]
194 fn load_directory_globals() {
195 let path = PathBuf::from(".")
196 .join("tests")
197 .join("config-dir")
198 .join("globals");
199 let configs = vec![ConfigPath::Dir(path)];
200 ConfigBuilderLoader::default()
201 .interpolate_env(true)
202 .load_from_paths(&configs)
203 .unwrap();
204 }
205
206 #[test]
207 fn load_directory_globals_duplicates() {
208 let path = PathBuf::from(".")
209 .join("tests")
210 .join("config-dir")
211 .join("globals-duplicate");
212 let configs = vec![ConfigPath::Dir(path)];
213 ConfigBuilderLoader::default()
214 .interpolate_env(true)
215 .load_from_paths(&configs)
216 .unwrap();
217 }
218}