support --exclude for job name
This commit is contained in:
parent
f825488351
commit
5637a552ea
5 changed files with 134 additions and 8 deletions
20
src/cli.rs
20
src/cli.rs
|
|
@ -36,6 +36,9 @@ pub enum RawMode {
|
||||||
},
|
},
|
||||||
Check {
|
Check {
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
|
/// Names of jobs to exclude from a catch-all check.
|
||||||
|
#[arg(long, num_args = 1.., value_name = "NAME")]
|
||||||
|
exclude: Vec<String>,
|
||||||
},
|
},
|
||||||
Lastlog {
|
Lastlog {
|
||||||
names: Vec<String>,
|
names: Vec<String>,
|
||||||
|
|
@ -154,7 +157,22 @@ mod tests {
|
||||||
fn check_with_names() {
|
fn check_with_names() {
|
||||||
let args = RawArgs::parse_from(["scriptherder", "check", "jobA", "jobB"]);
|
let args = RawArgs::parse_from(["scriptherder", "check", "jobA", "jobB"]);
|
||||||
match args.mode {
|
match args.mode {
|
||||||
RawMode::Check { names } => assert_eq!(names, vec!["jobA", "jobB"]),
|
RawMode::Check { names, exclude } => {
|
||||||
|
assert_eq!(names, vec!["jobA", "jobB"]);
|
||||||
|
assert!(exclude.is_empty());
|
||||||
|
}
|
||||||
|
_ => panic!("expected Check"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_parses_exclude() {
|
||||||
|
let args = RawArgs::parse_from(["scriptherder", "check", "--exclude", "cosmos", "backup"]);
|
||||||
|
match args.mode {
|
||||||
|
RawMode::Check { names, exclude } => {
|
||||||
|
assert!(names.is_empty());
|
||||||
|
assert_eq!(exclude, vec!["cosmos", "backup"]);
|
||||||
|
}
|
||||||
_ => panic!("expected Check"),
|
_ => panic!("expected Check"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ impl JobsList {
|
||||||
checkdir: &str,
|
checkdir: &str,
|
||||||
names: &[String],
|
names: &[String],
|
||||||
load_not_running: bool,
|
load_not_running: bool,
|
||||||
|
exclude: &[String],
|
||||||
) -> Result<JobsList, ScriptHerderError> {
|
) -> Result<JobsList, ScriptHerderError> {
|
||||||
let mut jobs: Vec<Job> = Vec::new();
|
let mut jobs: Vec<Job> = Vec::new();
|
||||||
if let Ok(entries) = std::fs::read_dir(datadir) {
|
if let Ok(entries) = std::fs::read_dir(datadir) {
|
||||||
|
|
@ -39,6 +40,10 @@ impl JobsList {
|
||||||
if !names.is_empty() && names != ["ALL"] && !names.contains(&job.name()) {
|
if !names.is_empty() && names != ["ALL"] && !names.contains(&job.name()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if exclude.contains(&job.name()) {
|
||||||
|
log::debug!("Excluding {:?} (file {})", job.name(), fname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
jobs.push(job);
|
jobs.push(job);
|
||||||
}
|
}
|
||||||
Err(exc) => {
|
Err(exc) => {
|
||||||
|
|
@ -53,12 +58,12 @@ impl JobsList {
|
||||||
}
|
}
|
||||||
let mut list = JobsList::from_jobs(jobs, load_not_running);
|
let mut list = JobsList::from_jobs(jobs, load_not_running);
|
||||||
if load_not_running {
|
if load_not_running {
|
||||||
list.load_not_running(checkdir, names);
|
list.load_not_running(checkdir, names, exclude);
|
||||||
}
|
}
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_not_running(&mut self, checkdir: &str, names: &[String]) {
|
fn load_not_running(&mut self, checkdir: &str, names: &[String], exclude: &[String]) {
|
||||||
let present: std::collections::HashSet<String> =
|
let present: std::collections::HashSet<String> =
|
||||||
self.jobs.iter().map(|j| j.name()).collect();
|
self.jobs.iter().map(|j| j.name()).collect();
|
||||||
if let Ok(entries) = std::fs::read_dir(checkdir) {
|
if let Ok(entries) = std::fs::read_dir(checkdir) {
|
||||||
|
|
@ -75,6 +80,10 @@ impl JobsList {
|
||||||
if !names.is_empty() && names != ["ALL"] && !names.contains(&name) {
|
if !names.is_empty() && names != ["ALL"] && !names.contains(&name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if exclude.contains(&name) {
|
||||||
|
log::debug!("Excluding not-running {name:?} (file {fname})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if !present.contains(&name) {
|
if !present.contains(&name) {
|
||||||
if let Ok(job) = Job::new(&name, vec![]) {
|
if let Ok(job) = Job::new(&name, vec![]) {
|
||||||
self.jobs.push(job);
|
self.jobs.push(job);
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ fn main() {
|
||||||
let code = match &args.mode {
|
let code = match &args.mode {
|
||||||
RawMode::Wrap { .. } => modes::wrap(&args),
|
RawMode::Wrap { .. } => modes::wrap(&args),
|
||||||
RawMode::Ls { names } => modes::ls(&args, names),
|
RawMode::Ls { names } => modes::ls(&args, names),
|
||||||
RawMode::Check { names } => modes::run_check(&args, names),
|
RawMode::Check { names, exclude } => modes::run_check(&args, names, exclude),
|
||||||
RawMode::Lastlog { names } => modes::lastlog(&args, names, false),
|
RawMode::Lastlog { names } => modes::lastlog(&args, names, false),
|
||||||
RawMode::Lastfaillog { names } => modes::lastlog(&args, names, true),
|
RawMode::Lastfaillog { names } => modes::lastlog(&args, names, true),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ pub fn wrap(args: &RawArgs) -> i32 {
|
||||||
|
|
||||||
/// `mode_ls` (../src/scriptherder.py:1153): list saved job states in a table.
|
/// `mode_ls` (../src/scriptherder.py:1153): list saved job states in a table.
|
||||||
pub fn ls(args: &RawArgs, names: &[String]) -> i32 {
|
pub fn ls(args: &RawArgs, names: &[String]) -> i32 {
|
||||||
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true) {
|
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true, &[]) {
|
||||||
Ok(j) => j,
|
Ok(j) => j,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed loading jobs: {} ({})", e.reason(), e.filename());
|
log::error!("Failed loading jobs: {} ({})", e.reason(), e.filename());
|
||||||
|
|
@ -173,8 +173,8 @@ pub fn ls(args: &RawArgs, names: &[String]) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `mode_check` (../src/scriptherder.py:1222): Nagios-style aggregate status.
|
/// `mode_check` (../src/scriptherder.py:1222): Nagios-style aggregate status.
|
||||||
pub fn run_check(args: &RawArgs, names: &[String]) -> i32 {
|
pub fn run_check(args: &RawArgs, names: &[String], exclude: &[String]) -> i32 {
|
||||||
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true) {
|
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true, exclude) {
|
||||||
Ok(j) => j,
|
Ok(j) => j,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -194,7 +194,7 @@ pub fn run_check(args: &RawArgs, names: &[String]) -> i32 {
|
||||||
|
|
||||||
/// `mode_lastlog` (../src/scriptherder.py:1244): print last (or last-failed) output.
|
/// `mode_lastlog` (../src/scriptherder.py:1244): print last (or last-failed) output.
|
||||||
pub fn lastlog(args: &RawArgs, names: &[String], fail_status: bool) -> i32 {
|
pub fn lastlog(args: &RawArgs, names: &[String], fail_status: bool) -> i32 {
|
||||||
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true) {
|
let jobs = match JobsList::from_dir(&args.datadir, &args.checkdir, names, true, &[]) {
|
||||||
Ok(j) => j,
|
Ok(j) => j,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed loading jobs: {} ({})", e.reason(), e.filename());
|
log::error!("Failed loading jobs: {} ({})", e.reason(), e.filename());
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,102 @@ fn lastlog_empty_datadir_no_jobs_exits_1() {
|
||||||
assert_eq!(out.status.code(), Some(1));
|
assert_eq!(out.status.code(), Some(1));
|
||||||
std::fs::remove_dir_all(&dir).ok();
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run `wrap -N <name> -- <cmd...>` into the given dirs.
|
||||||
|
fn wrap_job(datadir: &std::path::Path, checkdir: &std::path::Path, name: &str, cmd: &[&str]) {
|
||||||
|
let w = bin()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(datadir)
|
||||||
|
.arg("--checkdir")
|
||||||
|
.arg(checkdir)
|
||||||
|
.arg("wrap")
|
||||||
|
.arg("-N")
|
||||||
|
.arg(name)
|
||||||
|
.arg("--")
|
||||||
|
.args(cmd)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(w.status.success(), "wrap {name} failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `check --exclude` drops a failing job, flipping the aggregate from CRITICAL to OK.
|
||||||
|
#[test]
|
||||||
|
fn check_exclude_removes_failing_job() {
|
||||||
|
let dir = std::env::temp_dir().join(format!("sh_excl1_{}", std::process::id()));
|
||||||
|
let checkdir = dir.join("check");
|
||||||
|
std::fs::create_dir_all(&checkdir).unwrap();
|
||||||
|
std::fs::write(checkdir.join("alpha.ini"), "[check]\nok = exit_status=0\n").unwrap();
|
||||||
|
std::fs::write(checkdir.join("beta.ini"), "[check]\nok = exit_status=0\n").unwrap();
|
||||||
|
wrap_job(&dir, &checkdir, "alpha", &["/bin/true"]);
|
||||||
|
wrap_job(&dir, &checkdir, "beta", &["/bin/sh", "-c", "exit 1"]);
|
||||||
|
|
||||||
|
// Without exclude: beta is failing → CRITICAL.
|
||||||
|
let c = bin()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&dir)
|
||||||
|
.arg("--checkdir")
|
||||||
|
.arg(&checkdir)
|
||||||
|
.arg("check")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let stdout = String::from_utf8_lossy(&c.stdout);
|
||||||
|
assert!(stdout.starts_with("CRITICAL:"), "got: {stdout}");
|
||||||
|
assert_eq!(c.status.code(), Some(2));
|
||||||
|
|
||||||
|
// Excluding beta leaves only alpha (OK).
|
||||||
|
let c = bin()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&dir)
|
||||||
|
.arg("--checkdir")
|
||||||
|
.arg(&checkdir)
|
||||||
|
.arg("check")
|
||||||
|
.arg("--exclude")
|
||||||
|
.arg("beta")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let stdout = String::from_utf8_lossy(&c.stdout);
|
||||||
|
assert!(stdout.starts_with("OK:"), "got: {stdout}");
|
||||||
|
assert_eq!(c.status.code(), Some(0));
|
||||||
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `check --exclude` also drops a not-running job synthesized from a check `.ini`.
|
||||||
|
#[test]
|
||||||
|
fn check_exclude_removes_not_running() {
|
||||||
|
let dir = std::env::temp_dir().join(format!("sh_excl2_{}", std::process::id()));
|
||||||
|
let checkdir = dir.join("check");
|
||||||
|
std::fs::create_dir_all(&checkdir).unwrap();
|
||||||
|
std::fs::write(checkdir.join("alpha.ini"), "[check]\nok = exit_status=0\n").unwrap();
|
||||||
|
// stale.ini has no corresponding job → load_not_running synthesizes a CRITICAL job.
|
||||||
|
std::fs::write(checkdir.join("stale.ini"), "[check]\nok = exit_status=0\n").unwrap();
|
||||||
|
wrap_job(&dir, &checkdir, "alpha", &["/bin/true"]);
|
||||||
|
|
||||||
|
// Without exclude: stale is not running → CRITICAL.
|
||||||
|
let c = bin()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&dir)
|
||||||
|
.arg("--checkdir")
|
||||||
|
.arg(&checkdir)
|
||||||
|
.arg("check")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let stdout = String::from_utf8_lossy(&c.stdout);
|
||||||
|
assert!(stdout.starts_with("CRITICAL:"), "got: {stdout}");
|
||||||
|
assert_eq!(c.status.code(), Some(2));
|
||||||
|
|
||||||
|
// Excluding stale leaves only alpha (OK).
|
||||||
|
let c = bin()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(&dir)
|
||||||
|
.arg("--checkdir")
|
||||||
|
.arg(&checkdir)
|
||||||
|
.arg("check")
|
||||||
|
.arg("--exclude")
|
||||||
|
.arg("stale")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let stdout = String::from_utf8_lossy(&c.stdout);
|
||||||
|
assert!(stdout.starts_with("OK:"), "got: {stdout}");
|
||||||
|
assert_eq!(c.status.code(), Some(0));
|
||||||
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue