1use std::{
2 collections::BTreeSet, collections::HashMap, ffi::OsStr, fs, path::Path, sync::LazyLock,
3};
4
5use anyhow::{bail, Context, Result};
6use serde::Deserialize;
7use serde_json::Value;
8
9type ComponentMap = HashMap<String, Component>;
10
11type FeatureSet = BTreeSet<String>;
13
14macro_rules! mapping {
15 ( $( $key:ident => $value:ident, )* ) => {
16 HashMap::from([
17 $( (stringify!($key), stringify!($value)), )*
18 ])
19 };
20}
21
22static SOURCE_FEATURE_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
25 mapping!(
26 prometheus_pushgateway => prometheus,
27 prometheus_scrape => prometheus,
28 prometheus_remote_write => prometheus,
29 )
30});
31
32static TRANSFORM_FEATURE_MAP: LazyLock<HashMap<&'static str, &'static str>> =
33 LazyLock::new(|| mapping!());
34
35static SINK_FEATURE_MAP: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
36 mapping!(
37 gcp_pubsub => gcp,
38 gcp_cloud_storage => gcp,
39 gcp_stackdriver_logs => gcp,
40 gcp_stackdriver_metrics => gcp,
41 prometheus_exporter => prometheus,
42 prometheus_remote_write => prometheus,
43 splunk_hec_logs => splunk_hec,
44 )
45});
46
47#[derive(Deserialize)]
50pub struct VectorConfig {
51 api: Option<Value>,
52
53 #[serde(default)]
54 sources: ComponentMap,
55 #[serde(default)]
56 transforms: ComponentMap,
57 #[serde(default)]
58 sinks: ComponentMap,
59}
60
61#[derive(Deserialize)]
62struct Component {
63 r#type: String,
64}
65
66pub fn load_and_extract(filename: &Path) -> Result<Vec<String>> {
67 let config = fs::read_to_string(filename)
68 .with_context(|| format!("failed to read {}", filename.display()))?;
69
70 let config: VectorConfig = match filename
71 .extension()
72 .and_then(OsStr::to_str)
73 .map(str::to_lowercase)
74 .as_deref()
75 {
76 None => bail!("Invalid filename {filename:?}, no extension"),
77 Some("json") => serde_json::from_str(&config)?,
78 Some("toml") => toml::from_str(&config)?,
79 Some("yaml" | "yml") => serde_yaml::from_str(&config)?,
80 Some(_) => bail!("Invalid filename {filename:?}, unknown extension"),
81 };
82
83 Ok(from_config(config))
84}
85
86pub fn from_config(config: VectorConfig) -> Vec<String> {
87 let mut features = FeatureSet::default();
88 add_option(&mut features, "api", config.api.as_ref());
89
90 get_features(
91 &mut features,
92 "sources",
93 config.sources,
94 &SOURCE_FEATURE_MAP,
95 );
96 get_features(
97 &mut features,
98 "transforms",
99 config.transforms,
100 &TRANSFORM_FEATURE_MAP,
101 );
102 get_features(&mut features, "sinks", config.sinks, &SINK_FEATURE_MAP);
103
104 features.remove("transforms-log_to_metric");
107
108 features.into_iter().collect()
109}
110
111fn add_option<T>(features: &mut FeatureSet, name: &str, field: Option<&T>) {
112 if field.is_some() {
113 features.insert(name.into());
114 }
115}
116
117fn get_features(
120 features: &mut FeatureSet,
121 key: &str,
122 section: ComponentMap,
123 exceptions: &HashMap<&str, &str>,
124) {
125 features.extend(
126 section
127 .into_values()
128 .map(|component| component.r#type)
129 .map(|name| {
130 exceptions
131 .get(name.as_str())
132 .map_or(name, ToString::to_string)
133 })
134 .map(|name| format!("{key}-{name}")),
135 );
136}