Skip to content

Commit 3e499d2

Browse files
committed
feat(config): add FactConfigBuilder for better semantics
The new added FactConfigBuilder makes it obvious that the way of building a configuration is by parsing a set of files and putting together a FactConfig object from them and the CLI arguments. This improves semantics and makes the code more streamlined.
1 parent e219451 commit 3e499d2

File tree

7 files changed

+137
-123
lines changed

7 files changed

+137
-123
lines changed

fact/src/bpf.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,7 @@ mod bpf_tests {
217217
use tempfile::NamedTempFile;
218218
use tokio::{sync::watch, time::timeout};
219219

220-
use crate::{
221-
config::{reloader::Reloader, FactConfig},
222-
event::process::Process,
223-
host_info,
224-
metrics::exporter::Exporter,
225-
};
220+
use crate::{config, event::process::Process, host_info, metrics::exporter::Exporter};
226221

227222
use super::*;
228223

@@ -247,12 +242,10 @@ mod bpf_tests {
247242
let monitored_path = env!("CARGO_MANIFEST_DIR");
248243
let monitored_path = PathBuf::from(monitored_path);
249244
let paths = vec![monitored_path.clone()];
250-
let mut config = FactConfig::default();
251-
config.set_paths(paths);
252-
let reloader = Reloader::from(config);
245+
let (_, paths) = watch::channel(paths);
253246
executor.block_on(async {
254-
let mut bpf = Bpf::new(reloader.paths(), reloader.config().ringbuf_size())
255-
.expect("Failed to load BPF code");
247+
let mut bpf =
248+
Bpf::new(paths, config::DEFAULT_RINGBUFFER_SIZE).expect("Failed to load BPF code");
256249
let mut rx = bpf.subscribe();
257250
let (run_tx, run_rx) = watch::channel(true);
258251
// Create a metrics exporter, but don't start it

fact/src/config/builder.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::{fs::read_to_string, path::PathBuf, sync::LazyLock};
2+
3+
use anyhow::Context;
4+
use clap::Parser;
5+
6+
use crate::config::FactCli;
7+
8+
use super::FactConfig;
9+
10+
pub(super) struct FactConfigBuilder {
11+
files: Vec<PathBuf>,
12+
}
13+
14+
impl FactConfigBuilder {
15+
pub(super) fn new() -> Self {
16+
let files = Vec::new();
17+
FactConfigBuilder { files }
18+
}
19+
20+
pub(super) fn add_files(
21+
mut self,
22+
files: &[impl Into<PathBuf> + AsRef<std::ffi::OsStr>],
23+
) -> Self {
24+
for file in files {
25+
self.files.push(file.into());
26+
}
27+
self
28+
}
29+
30+
pub(super) fn files(&self) -> &[PathBuf] {
31+
self.files.as_slice()
32+
}
33+
34+
pub(super) fn build(&self) -> anyhow::Result<FactConfig> {
35+
let mut config = self
36+
.files
37+
.iter()
38+
.filter(|p| p.exists())
39+
.map(|p| {
40+
let content =
41+
read_to_string(p).with_context(|| format!("Failed to read {}", p.display()))?;
42+
FactConfig::try_from(content.as_str())
43+
.with_context(|| format!("parsing error while processing {}", p.display()))
44+
})
45+
.try_fold(
46+
FactConfig::default(),
47+
|mut config: FactConfig, other: anyhow::Result<FactConfig>| {
48+
config.update(&other?);
49+
Ok::<FactConfig, anyhow::Error>(config)
50+
},
51+
)?;
52+
53+
// Once file configuration is handled, apply CLI arguments
54+
static CLI_ARGS: LazyLock<FactConfig> = LazyLock::new(|| FactCli::parse().to_config());
55+
config.update(&CLI_ARGS);
56+
57+
Ok(config)
58+
}
59+
}

fact/src/config/mod.rs

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
use std::{
2-
fs::read_to_string,
32
net::SocketAddr,
43
path::{Path, PathBuf},
54
str::FromStr,
6-
sync::LazyLock,
75
};
86

9-
use anyhow::{bail, Context};
7+
use anyhow::bail;
108
use clap::Parser;
11-
use log::info;
129
use yaml_rust2::{yaml, Yaml, YamlLoader};
1310

11+
mod builder;
1412
pub mod reloader;
1513
#[cfg(test)]
1614
mod tests;
1715

18-
const CONFIG_FILES: [&str; 4] = [
19-
"/etc/stackrox/fact.yml",
20-
"/etc/stackrox/fact.yaml",
21-
"fact.yml",
22-
"fact.yaml",
23-
];
16+
pub const DEFAULT_RINGBUFFER_SIZE: u32 = 8192;
2417

2518
#[derive(Debug, Default, PartialEq, Eq, Clone)]
2619
pub struct FactConfig {
@@ -34,44 +27,6 @@ pub struct FactConfig {
3427
}
3528

3629
impl FactConfig {
37-
pub fn new() -> anyhow::Result<Self> {
38-
let config = FactConfig::build()?;
39-
info!("{config:#?}");
40-
Ok(config)
41-
}
42-
43-
fn build() -> anyhow::Result<FactConfig> {
44-
let mut config = CONFIG_FILES
45-
.iter()
46-
.filter_map(|p| {
47-
let p = Path::new(p);
48-
if p.exists() {
49-
Some(p)
50-
} else {
51-
None
52-
}
53-
})
54-
.map(|p| {
55-
let content =
56-
read_to_string(p).with_context(|| format!("Failed to read {}", p.display()))?;
57-
FactConfig::try_from(content.as_str())
58-
.with_context(|| format!("parsing error while processing {}", p.display()))
59-
})
60-
.try_fold(
61-
FactConfig::default(),
62-
|mut config: FactConfig, other: anyhow::Result<FactConfig>| {
63-
config.update(&other?);
64-
Ok::<FactConfig, anyhow::Error>(config)
65-
},
66-
)?;
67-
68-
// Once file configuration is handled, apply CLI arguments
69-
static CLI_ARGS: LazyLock<FactConfig> = LazyLock::new(|| FactCli::parse().to_config());
70-
config.update(&CLI_ARGS);
71-
72-
Ok(config)
73-
}
74-
7530
pub fn update(&mut self, from: &FactConfig) {
7631
if let Some(paths) = from.paths.as_deref() {
7732
self.paths = Some(paths.to_owned());
@@ -110,7 +65,7 @@ impl FactConfig {
11065
}
11166

11267
pub fn ringbuf_size(&self) -> u32 {
113-
self.ringbuf_size.unwrap_or(8192)
68+
self.ringbuf_size.unwrap_or(DEFAULT_RINGBUFFER_SIZE)
11469
}
11570

11671
pub fn hotreload(&self) -> bool {

fact/src/config/reloader.rs

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,66 @@ use tokio::{
99
time::interval,
1010
};
1111

12-
use super::{EndpointConfig, FactConfig, GrpcConfig, CONFIG_FILES};
12+
use super::{builder::FactConfigBuilder, EndpointConfig, FactConfig, GrpcConfig};
13+
14+
const CONFIG_FILES: [&str; 4] = [
15+
"/etc/stackrox/fact.yml",
16+
"/etc/stackrox/fact.yaml",
17+
"fact.yml",
18+
"fact.yaml",
19+
];
1320

1421
pub struct Reloader {
1522
config: FactConfig,
23+
builder: FactConfigBuilder,
1624
endpoint: watch::Sender<EndpointConfig>,
1725
grpc: watch::Sender<GrpcConfig>,
1826
paths: watch::Sender<Vec<PathBuf>>,
19-
files: HashMap<&'static str, i64>,
27+
files: HashMap<PathBuf, i64>,
2028
trigger: Arc<Notify>,
2129
}
2230

2331
impl Reloader {
32+
pub fn new() -> anyhow::Result<Self> {
33+
let builder = FactConfigBuilder::new().add_files(CONFIG_FILES.as_slice());
34+
let config = builder.build()?;
35+
info!("Startup configuration: {config:#?}");
36+
37+
let (endpoint, _) = watch::channel(config.endpoint.clone());
38+
let (grpc, _) = watch::channel(config.grpc.clone());
39+
let (paths, _) = watch::channel(config.paths().to_vec());
40+
let trigger = Arc::new(Notify::new());
41+
let files = builder
42+
.files()
43+
.iter()
44+
.filter_map(|path| {
45+
if path.exists() {
46+
let mtime = match path.metadata() {
47+
Ok(m) => m.mtime(),
48+
Err(e) => {
49+
warn!("Failed to stat {}: {e}", path.display());
50+
warn!("Configuration reloading may not work");
51+
return None;
52+
}
53+
};
54+
Some((path.clone(), mtime))
55+
} else {
56+
None
57+
}
58+
})
59+
.collect();
60+
61+
Ok(Reloader {
62+
config,
63+
builder,
64+
endpoint,
65+
grpc,
66+
paths,
67+
files,
68+
trigger,
69+
})
70+
}
71+
2472
/// Consume the reloader into a task
2573
///
2674
/// The resulting task will handle reloading the configuration and
@@ -91,34 +139,33 @@ impl Reloader {
91139
fn update_cache(&mut self) -> bool {
92140
let mut res = false;
93141

94-
for file in CONFIG_FILES {
95-
let path = PathBuf::from(file);
96-
if path.exists() {
97-
let mtime = match path.metadata() {
142+
for file in self.builder.files() {
143+
if file.exists() {
144+
let mtime = match file.metadata() {
98145
Ok(m) => m.mtime(),
99146
Err(e) => {
100-
warn!("Failed to stat {file}: {e}");
147+
warn!("Failed to stat {}: {e}", file.display());
101148
warn!("Configuration reloading may not work");
102149
continue;
103150
}
104151
};
105-
match self.files.get_mut(&file) {
152+
match self.files.get_mut(file) {
106153
Some(old) if *old == mtime => {}
107154
Some(old) => {
108-
debug!("Updating '{file}'");
155+
debug!("Updating '{}'", file.display());
109156
res = true;
110157
*old = mtime;
111158
}
112159
None => {
113-
debug!("New configuration file '{file}'");
160+
debug!("New configuration file '{}'", file.display());
114161
res = true;
115-
self.files.insert(file, mtime);
162+
self.files.insert(file.clone(), mtime);
116163
}
117164
}
118-
} else if self.files.contains_key(&file) {
119-
debug!("'{file}' no longer exists, removing from cache");
165+
} else if self.files.contains_key(file) {
166+
debug!("'{}' no longer exists, removing from cache", file.display());
120167
res = true;
121-
self.files.remove(&file);
168+
self.files.remove(file);
122169
}
123170
}
124171
res
@@ -131,7 +178,7 @@ impl Reloader {
131178
return;
132179
}
133180

134-
let new = match FactConfig::build() {
181+
let new = match self.builder.build() {
135182
Ok(config) => config,
136183
Err(e) => {
137184
warn!("Configuration reloading failed: {e}");
@@ -178,40 +225,3 @@ impl Reloader {
178225
self.config = new;
179226
}
180227
}
181-
182-
impl From<FactConfig> for Reloader {
183-
fn from(config: FactConfig) -> Self {
184-
let files = CONFIG_FILES
185-
.iter()
186-
.filter_map(|path| {
187-
let p = PathBuf::from(path);
188-
if p.exists() {
189-
let mtime = match p.metadata() {
190-
Ok(m) => m.mtime(),
191-
Err(e) => {
192-
warn!("Failed to stat {path}: {e}");
193-
warn!("Configuration reloading may not work");
194-
return None;
195-
}
196-
};
197-
Some((*path, mtime))
198-
} else {
199-
None
200-
}
201-
})
202-
.collect();
203-
let (endpoint, _) = watch::channel(config.endpoint.clone());
204-
let (grpc, _) = watch::channel(config.grpc.clone());
205-
let (paths, _) = watch::channel(config.paths().to_vec());
206-
let trigger = Arc::new(Notify::new());
207-
208-
Reloader {
209-
config,
210-
endpoint,
211-
grpc,
212-
paths,
213-
files,
214-
trigger,
215-
}
216-
}
217-
}

fact/src/config/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,6 @@ fn defaults() {
831831
assert!(!config.endpoint.health_check());
832832
assert!(!config.skip_pre_flight());
833833
assert!(!config.json());
834-
assert_eq!(config.ringbuf_size(), 8192);
834+
assert_eq!(config.ringbuf_size(), DEFAULT_RINGBUFFER_SIZE);
835835
assert!(config.hotreload());
836836
}

fact/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ mod metrics;
1919
mod output;
2020
mod pre_flight;
2121

22-
use config::FactConfig;
22+
use config::reloader::Reloader;
2323
use pre_flight::pre_flight;
2424

2525
pub fn init_log() -> anyhow::Result<()> {
@@ -60,22 +60,22 @@ pub fn log_system_information() {
6060
info!("Hostname: {}", get_hostname());
6161
}
6262

63-
pub async fn run(config: FactConfig) -> anyhow::Result<()> {
63+
pub async fn run() -> anyhow::Result<()> {
64+
let reloader = Reloader::new()?;
65+
let config_trigger = reloader.get_trigger();
66+
6467
// Log system information as early as possible so we have it
6568
// available in case of a crash
6669
log_system_information();
6770
let (running, _) = watch::channel(true);
6871

69-
if !config.skip_pre_flight() {
72+
if !reloader.config().skip_pre_flight() {
7073
debug!("Performing pre-flight checks");
7174
pre_flight().context("Pre-flight checks failed")?;
7275
} else {
7376
debug!("Skipping pre-flight checks");
7477
}
7578

76-
let reloader = config::reloader::Reloader::from(config);
77-
let config_trigger = reloader.get_trigger();
78-
7979
let mut bpf = Bpf::new(reloader.paths(), reloader.config().ringbuf_size())?;
8080
let exporter = Exporter::new(bpf.take_metrics()?);
8181

fact/src/main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use fact::config::FactConfig;
2-
31
#[tokio::main]
42
async fn main() -> anyhow::Result<()> {
53
fact::init_log()?;
6-
let config = FactConfig::new()?;
74

8-
fact::run(config).await
5+
fact::run().await
96
}

0 commit comments

Comments
 (0)