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 {
60 Self {
61 builder: ConfigBuilder::default(),
62 secrets: HashMap::new(),
63 interpolate_env: true,
64 }
65 }
66}
67
68impl Process for ConfigBuilderLoader {
69 fn prepare<R: Read>(&mut self, input: R) -> Result<String, Vec<String>> {
71 let prepared_input = prepare_input(input, self.interpolate_env)?;
72 Ok(if self.secrets.is_empty() {
73 prepared_input
74 } else {
75 secret::interpolate(&prepared_input, &self.secrets)?
76 })
77 }
78
79 fn merge(&mut self, table: Table, hint: Option<ComponentHint>) -> Result<(), Vec<String>> {
81 match hint {
82 Some(ComponentHint::Source) => {
83 self.builder.sources.extend(deserialize_table::<
84 IndexMap<ComponentKey, SourceOuter>,
85 >(table)?);
86 }
87 Some(ComponentHint::Sink) => {
88 self.builder.sinks.extend(
89 deserialize_table::<IndexMap<ComponentKey, SinkOuter<_>>>(table)?,
90 );
91 }
92 Some(ComponentHint::Transform) => {
93 self.builder.transforms.extend(deserialize_table::<
94 IndexMap<ComponentKey, TransformOuter<_>>,
95 >(table)?);
96 }
97 Some(ComponentHint::EnrichmentTable) => {
98 self.builder.enrichment_tables.extend(deserialize_table::<
99 IndexMap<ComponentKey, EnrichmentTableOuter<_>>,
100 >(table)?);
101 }
102 Some(ComponentHint::Test) => {
103 self.builder.tests.extend(
106 deserialize_table::<IndexMap<String, TestDefinition<String>>>(table)?
107 .into_iter()
108 .map(|(_, test)| test),
109 );
110 }
111 None => {
112 self.builder.append(deserialize_table(table)?)?;
113 }
114 };
115
116 Ok(())
117 }
118}
119
120impl loader::Loader<ConfigBuilder> for ConfigBuilderLoader {
121 fn take(self) -> ConfigBuilder {
123 self.builder
124 }
125}
126
127#[cfg(all(
128 test,
129 feature = "sinks-elasticsearch",
130 feature = "transforms-sample",
131 feature = "sources-demo_logs",
132 feature = "sinks-console"
133))]
134mod tests {
135 use std::path::PathBuf;
136
137 use super::ConfigBuilderLoader;
138 use crate::config::{ComponentKey, ConfigPath};
139
140 #[test]
141 fn load_namespacing_folder() {
142 let path = PathBuf::from(".")
143 .join("tests")
144 .join("namespacing")
145 .join("success");
146 let configs = vec![ConfigPath::Dir(path)];
147 let builder = ConfigBuilderLoader::default()
148 .interpolate_env(true)
149 .load_from_paths(&configs)
150 .unwrap();
151 assert!(
152 builder
153 .transforms
154 .contains_key(&ComponentKey::from("apache_parser"))
155 );
156 assert!(
157 builder
158 .sources
159 .contains_key(&ComponentKey::from("apache_logs"))
160 );
161 assert!(
162 builder
163 .sinks
164 .contains_key(&ComponentKey::from("es_cluster"))
165 );
166 assert_eq!(builder.tests.len(), 2);
167 }
168
169 #[test]
170 fn load_namespacing_ignore_invalid() {
171 let path = PathBuf::from(".")
172 .join("tests")
173 .join("namespacing")
174 .join("ignore-invalid");
175 let configs = vec![ConfigPath::Dir(path)];
176 ConfigBuilderLoader::default()
177 .interpolate_env(true)
178 .load_from_paths(&configs)
179 .unwrap();
180 }
181
182 #[test]
183 fn load_directory_ignores_unknown_file_formats() {
184 let path = PathBuf::from(".")
185 .join("tests")
186 .join("config-dir")
187 .join("ignore-unknown");
188 let configs = vec![ConfigPath::Dir(path)];
189 ConfigBuilderLoader::default()
190 .interpolate_env(true)
191 .load_from_paths(&configs)
192 .unwrap();
193 }
194
195 #[test]
196 fn load_directory_globals() {
197 let path = PathBuf::from(".")
198 .join("tests")
199 .join("config-dir")
200 .join("globals");
201 let configs = vec![ConfigPath::Dir(path)];
202 ConfigBuilderLoader::default()
203 .interpolate_env(true)
204 .load_from_paths(&configs)
205 .unwrap();
206 }
207
208 #[test]
209 fn load_directory_globals_duplicates() {
210 let path = PathBuf::from(".")
211 .join("tests")
212 .join("config-dir")
213 .join("globals-duplicate");
214 let configs = vec![ConfigPath::Dir(path)];
215 ConfigBuilderLoader::default()
216 .interpolate_env(true)
217 .load_from_paths(&configs)
218 .unwrap();
219 }
220}