mod clock;
mod config;
mod delivery;
mod event_loop;
pub mod rules;
pub mod sinks;
mod source;
#[cfg(test)]
mod testing;
pub use config::BarkConfig;
use std::sync::Arc;
use config::rules_for;
use event_loop::run_loop;
use shep_client::dogs::Stop;
use super::DogRuntime;
use crate::exit::ExitCode;
fn subscription_topics(dog: &str, rules: &rules::Rules) -> Vec<String> {
let mut topics = vec!["process.*".to_owned(), format!("config.dog.{dog}")];
topics.extend(rules.log_topics());
topics
}
pub async fn run(runtime: DogRuntime) -> ExitCode {
let config = match runtime.config::<BarkConfig>() {
Ok(config) => config,
Err(err) => {
eprintln!("shep dog bark: {err}; see `shep dogs`");
return ExitCode::InvalidConfig;
}
};
let rules = match rules_for(&config) {
Ok(rules) => rules,
Err(err) => {
eprintln!("shep dog bark: {err}");
return ExitCode::InvalidConfig;
}
};
let dog = runtime.identity().section().to_owned();
let topics = subscription_topics(&dog, &rules);
let barks_path = runtime.paths().barks.clone();
let (events, shepherd) = match source::subscribe(runtime.into_client(), dog, topics).await {
Ok(subscribed) => subscribed,
Err(err) => {
eprintln!("shep dog bark: could not subscribe to the shepherd's bus: {err}");
return ExitCode::from(&err);
}
};
run_loop(
events,
Arc::clone(&shepherd),
rules,
&config,
&barks_path,
shepherd,
Stop::on_stop_signals(),
)
.await
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use shep_client::testing::{Handshake, fake_daemon_across_handovers, sample_ack};
use shep_core::protocol::Request;
use crate::dog::run_dog;
use crate::dog::tests::test_paths;
async fn subscribed_topics(section: &str) -> Vec<String> {
let dir = tempfile::tempdir().unwrap();
let socket = shep_client::testing::control_address(dir.path());
let daemon = fake_daemon_across_handovers(&socket, vec![Handshake::Accept(sample_ack())]);
daemon.reply_to_dog_config(section);
let paths = test_paths(dir.path(), socket);
let task = tokio::spawn(run_dog("bark", paths));
let topics =
tokio::time::timeout(Duration::from_secs(5), async {
loop {
let subscribed = daemon.envelopes().into_iter().find_map(|(_, envelope)| {
match envelope.body {
Request::Subscribe { topics } => Some(topics),
_ => None,
}
});
if let Some(topics) = subscribed {
break topics;
}
tokio::task::yield_now().await;
}
})
.await
.expect("bark must subscribe once its config parses");
task.abort();
topics
}
const SINK: &str = "[sinks.ops]\nkind = \"json\"\nurl = \"http://127.0.0.1:1/hook\"\n";
#[tokio::test]
async fn bark_subscribes_to_its_own_config_topic() {
let topics = subscribed_topics(SINK).await;
assert!(
topics.iter().any(|topic| topic == "config.dog.bark"),
"bark must ask for its own config topic: {topics:?}"
);
assert!(
topics.iter().any(|topic| topic == "process.*"),
"the lifecycle topics every rule reads must survive: {topics:?}"
);
assert_eq!(
topics,
["process.*", "config.dog.bark"],
"with no log rule, nothing else"
);
}
#[tokio::test]
async fn a_stderr_log_rule_subscribes_to_log_err_alone() {
let section = format!(
"{SINK}[[rules]]\non = \"log_match\"\npattern = \"FATAL\"\nstream = \"err\"\nsinks = [\"ops\"]\n"
);
let topics = subscribed_topics(§ion).await;
assert!(topics.iter().any(|topic| topic == "log.err"), "{topics:?}");
assert!(!topics.iter().any(|topic| topic == "log.out"), "{topics:?}");
assert!(!topics.iter().any(|topic| topic == "log.*"), "{topics:?}");
}
fn rules_from(toml_rules: &str) -> super::rules::Rules {
let config: super::BarkConfig = toml::from_str(&format!("{SINK}{toml_rules}")).unwrap();
super::rules_for(&config).unwrap()
}
#[test]
fn the_topics_name_a_log_stream_only_while_a_rule_reads_it() {
let rule = |stream: &str| {
format!(
"[[rules]]\non = \"log_match\"\npattern = \"x\"\nstream = \"{stream}\"\nsinks = [\"ops\"]\n"
)
};
let base = ["process.*", "config.dog.bark"];
assert_eq!(super::subscription_topics("bark", &rules_from("")), base);
assert_eq!(
super::subscription_topics("bark", &rules_from(&rule("err"))),
[base[0], base[1], "log.err"]
);
assert_eq!(
super::subscription_topics("bark", &rules_from(&rule("out"))),
[base[0], base[1], "log.out"]
);
assert_eq!(
super::subscription_topics("bark", &rules_from(&rule("both"))),
[base[0], base[1], "log.out", "log.err"]
);
}
}