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