Skip to content

Commit cfbdf99

Browse files
committed
feat(operator): expose all fact configuration values
This requires some refactoring to re-use the FactConfig struct as part of the CRD spec for fact. We also make all configuration apply through a config map, which means we can hotreload the configuration as required. Still needs more testing, but so far it looks good.
1 parent 5095ff7 commit cfbdf99

20 files changed

Lines changed: 297 additions & 44 deletions

File tree

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "2"
33
members = [
44
"fact",
55
"fact-api",
6+
"fact-core",
67
"fact-ebpf",
78
"fact-operator",
89
]
@@ -31,6 +32,7 @@ openssl = "0.10.75"
3132
prometheus-client = { version = "0.24.0", default-features = false }
3233
prost = "0.14.0"
3334
prost-types = "0.14.0"
35+
schemars = { version = "1" }
3436
serde = { version = "1.0.219", features = ["derive"] }
3537
serde_json = "1.0.142"
3638
shlex = "2.0.1"

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ version:
99
image-name:
1010
@echo "$(FACT_IMAGE_NAME)"
1111

12+
operator-name:
13+
@echo "$(FACT_OPERATOR_NAME)"
14+
1215
mock-server:
1316
make -C mock-server
1417

@@ -66,4 +69,5 @@ format:
6669
make -C fact-ebpf format
6770
ruff format tests/
6871

69-
.PHONY: tag mock-server integration-tests image image-name licenses coverage lint clean
72+
.PHONY: tag mock-server integration-tests image images image-name
73+
.PHONY: operator operator-name licenses coverage lint clean

fact-core/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "fact-core"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license.workspace = true
6+
7+
[dependencies]
8+
anyhow = { workspace = true }
9+
clap = { workspace = true }
10+
log = { workspace = true }
11+
schemars = { workspace = true }
12+
serde = { workspace = true }
13+
tokio = { workspace = true }
14+
yaml-rust2 = { workspace = true }
15+
16+
[build-dependencies]
17+
anyhow = { workspace = true }
File renamed without changes.
Lines changed: 142 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::{
1010
use anyhow::{Context, bail};
1111
use clap::Parser;
1212
use log::info;
13+
use schemars::JsonSchema;
14+
use serde::{Deserialize, Serialize};
1315
use yaml_rust2::{Yaml, YamlLoader, yaml};
1416

1517
pub mod reloader;
@@ -23,11 +25,14 @@ const CONFIG_FILES: [&str; 4] = [
2325
"fact.yaml",
2426
];
2527

26-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
28+
#[derive(Debug, Default, PartialEq, Eq, Clone, JsonSchema, Serialize, Deserialize)]
2729
pub struct FactConfig {
2830
paths: Option<Vec<PathBuf>>,
31+
#[serde(default)]
2932
pub grpc: GrpcConfig,
33+
#[serde(default)]
3034
pub endpoint: EndpointConfig,
35+
#[serde(default)]
3136
pub bpf: BpfConfig,
3237
skip_pre_flight: Option<bool>,
3338
json: Option<bool>,
@@ -125,10 +130,56 @@ impl FactConfig {
125130
self.rate_limit.unwrap_or(0)
126131
}
127132

128-
#[cfg(test)]
129133
pub fn set_paths(&mut self, paths: Vec<PathBuf>) {
130134
self.paths = Some(paths);
131135
}
136+
137+
pub fn to_yaml(&self) -> Yaml {
138+
let mut config = yaml::Hash::new();
139+
140+
if let Some(paths) = &self.paths {
141+
let paths = paths
142+
.iter()
143+
.filter_map(|p| p.to_str().map(|p| Yaml::String(p.to_string())))
144+
.collect::<Vec<_>>();
145+
config.insert(Yaml::String("paths".into()), Yaml::Array(paths));
146+
}
147+
148+
config.insert(Yaml::String("grpc".into()), self.grpc.to_yaml());
149+
config.insert(Yaml::String("endpoint".into()), self.endpoint.to_yaml());
150+
config.insert(Yaml::String("bpf".into()), self.bpf.to_yaml());
151+
152+
if let Some(skip_pre_flight) = self.skip_pre_flight {
153+
config.insert(
154+
Yaml::String("skip_pre_flight".into()),
155+
Yaml::Boolean(skip_pre_flight),
156+
);
157+
}
158+
159+
if let Some(json) = self.skip_pre_flight {
160+
config.insert(Yaml::String("json".into()), Yaml::Boolean(json));
161+
}
162+
163+
if let Some(hotreload) = self.hotreload {
164+
config.insert(Yaml::String("hotreload".into()), Yaml::Boolean(hotreload));
165+
}
166+
167+
if let Some(scan_interval) = self.scan_interval {
168+
config.insert(
169+
Yaml::String("scan_interval".into()),
170+
Yaml::Integer(scan_interval.as_secs() as i64),
171+
);
172+
}
173+
174+
if let Some(rate_limit) = self.rate_limit {
175+
config.insert(
176+
Yaml::String("rate_limit".into()),
177+
Yaml::Integer(rate_limit as i64),
178+
);
179+
}
180+
181+
Yaml::Hash(config)
182+
}
132183
}
133184

134185
impl TryFrom<&str> for FactConfig {
@@ -189,10 +240,16 @@ impl TryFrom<Vec<Yaml>> for FactConfig {
189240
let grpc = v.as_hash().unwrap();
190241
config.grpc = GrpcConfig::try_from(grpc)?;
191242
}
243+
"grpc" if v.is_null() => {
244+
// Nothing to do
245+
}
192246
"endpoint" if v.is_hash() => {
193247
let endpoint = v.as_hash().unwrap();
194248
config.endpoint = EndpointConfig::try_from(endpoint)?;
195249
}
250+
"endpoint" if v.is_null() => {
251+
// Nothing to do
252+
}
196253
"skip_pre_flight" => {
197254
let Some(spf) = v.as_bool() else {
198255
bail!("skip_pre_flight field has incorrect type: {v:?}");
@@ -205,12 +262,13 @@ impl TryFrom<Vec<Yaml>> for FactConfig {
205262
};
206263
config.json = Some(json);
207264
}
208-
"bpf" => {
209-
let Some(bpf) = v.as_hash() else {
210-
bail!("bpf section has incorrect type: {v:#?}");
211-
};
265+
"bpf" if v.is_hash() => {
266+
let bpf = v.as_hash().unwrap();
212267
config.bpf = BpfConfig::try_from(bpf)?;
213268
}
269+
"bpf" if v.is_null() => {
270+
// Nothing to do
271+
}
214272
"hotreload" => {
215273
let Some(hotreload) = v.as_bool() else {
216274
bail!("hotreload field has incorrect type: {v:?}");
@@ -251,7 +309,7 @@ impl TryFrom<Vec<Yaml>> for FactConfig {
251309
}
252310
}
253311

254-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
312+
#[derive(Debug, Default, PartialEq, Eq, Clone, JsonSchema, Serialize, Deserialize)]
255313
pub struct EndpointConfig {
256314
address: Option<SocketAddr>,
257315
expose_metrics: Option<bool>,
@@ -285,6 +343,37 @@ impl EndpointConfig {
285343
pub fn health_check(&self) -> bool {
286344
self.health_check.unwrap_or(false)
287345
}
346+
347+
fn to_yaml(&self) -> Yaml {
348+
let mut endpoint = yaml::Hash::new();
349+
350+
if let Some(address) = self.address {
351+
endpoint.insert(
352+
Yaml::String("address".into()),
353+
Yaml::String(address.to_string()),
354+
);
355+
}
356+
357+
if let Some(expose_metrics) = self.expose_metrics {
358+
endpoint.insert(
359+
Yaml::String("expose_metrics".into()),
360+
Yaml::Boolean(expose_metrics),
361+
);
362+
}
363+
364+
if let Some(health_check) = self.health_check {
365+
endpoint.insert(
366+
Yaml::String("health_check".into()),
367+
Yaml::Boolean(health_check),
368+
);
369+
}
370+
371+
if !endpoint.is_empty() {
372+
Yaml::Hash(endpoint)
373+
} else {
374+
Yaml::Null
375+
}
376+
}
288377
}
289378

290379
impl TryFrom<&yaml::Hash> for EndpointConfig {
@@ -328,7 +417,7 @@ impl TryFrom<&yaml::Hash> for EndpointConfig {
328417
}
329418
}
330419

331-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
420+
#[derive(Debug, Default, PartialEq, Eq, Clone, JsonSchema, Serialize, Deserialize)]
332421
pub struct GrpcConfig {
333422
url: Option<String>,
334423
certs: Option<PathBuf>,
@@ -352,6 +441,26 @@ impl GrpcConfig {
352441
pub fn certs(&self) -> Option<&Path> {
353442
self.certs.as_deref()
354443
}
444+
445+
fn to_yaml(&self) -> Yaml {
446+
let mut grpc = yaml::Hash::new();
447+
448+
if let Some(url) = &self.url {
449+
grpc.insert(Yaml::String("url".into()), Yaml::String(url.clone()));
450+
}
451+
452+
if let Some(certs) = &self.certs
453+
&& let Some(certs) = certs.to_str()
454+
{
455+
grpc.insert(Yaml::String("certs".into()), Yaml::String(certs.into()));
456+
}
457+
458+
if !grpc.is_empty() {
459+
Yaml::Hash(grpc)
460+
} else {
461+
Yaml::Null
462+
}
463+
}
355464
}
356465

357466
impl TryFrom<&yaml::Hash> for GrpcConfig {
@@ -385,7 +494,7 @@ impl TryFrom<&yaml::Hash> for GrpcConfig {
385494
}
386495
}
387496

388-
#[derive(Debug, Default, PartialEq, Eq, Clone)]
497+
#[derive(Debug, Default, PartialEq, Eq, Clone, JsonSchema, Serialize, Deserialize)]
389498
pub struct BpfConfig {
390499
ringbuf_size: Option<u32>,
391500
inodes_max: Option<u32>,
@@ -409,6 +518,30 @@ impl BpfConfig {
409518
pub fn inodes_max(&self) -> u32 {
410519
self.inodes_max.unwrap_or(65536)
411520
}
521+
522+
fn to_yaml(&self) -> Yaml {
523+
let mut bpf = yaml::Hash::new();
524+
525+
if let Some(ringbuf_size) = self.ringbuf_size {
526+
bpf.insert(
527+
Yaml::String("ringbuf_size".into()),
528+
Yaml::Integer(ringbuf_size as i64),
529+
);
530+
}
531+
532+
if let Some(inodes_max) = self.ringbuf_size {
533+
bpf.insert(
534+
Yaml::String("inodes_max".into()),
535+
Yaml::Integer(inodes_max as i64),
536+
);
537+
}
538+
539+
if !bpf.is_empty() {
540+
Yaml::Hash(bpf)
541+
} else {
542+
Yaml::Null
543+
}
544+
}
412545
}
413546

414547
impl TryFrom<&yaml::Hash> for BpfConfig {

fact-core/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use crate::version::FACT_VERSION;
2+
3+
pub mod config;
4+
5+
mod version {
6+
include!(concat!(env!("OUT_DIR"), "/version.rs"));
7+
}
8+
9+
pub fn version() -> &'static str {
10+
FACT_VERSION
11+
}

fact-operator/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ futures = { version = "0.3.32", default-features = false }
1111
kube = { version = "4.0.0", default-features = true, features = ["client", "openssl-tls", "derive", "runtime"] }
1212
k8s-openapi = { version = "0.28.0", features = ["latest", "schemars"] }
1313
log = { workspace = true }
14-
schemars = { version = "1" }
14+
schemars = { workspace = true }
1515
serde = { workspace = true }
1616
serde_json = { workspace = true }
1717
tokio = { workspace = true }
18+
yaml-rust2 = { workspace = true }
19+
20+
fact-core = { path = "../fact-core" }

0 commit comments

Comments
 (0)