use std::time::Duration;
use shep_client::{Client, RELOAD_DEADLINE, START_DEADLINE};
use shep_core::paths::ShepPaths;
use shep_core::protocol::{ProcessInfo, Request, Response, SheepRefusal};
use crate::cli::{Format, SelectorArgs, StockArgs};
use crate::commands::dogs;
use crate::commands::lifecycle::selectors::{parse_selectors, render_outcome, request_each};
use crate::commands::rpc::request_payload;
use crate::exit::ExitCode;
use crate::output::{DeletedIds, FlockRows, Streams, emit_partial, write_outcome};
pub async fn stop(client: &Client, streams: &mut Streams<'_>, args: &SelectorArgs) -> ExitCode {
let selectors = match parse_selectors(streams, &args.selectors) {
Ok(selectors) => selectors,
Err(code) => return code,
};
let (procs, failure) = request_each(
client,
streams,
&selectors,
None,
|selector| Request::Stop { selector },
|response| match response {
Response::Stopped(procs) => Some(procs),
_ => None,
},
)
.await;
if !procs.is_empty() || failure.is_none() {
let wrote = render_outcome(client, streams, "stop", FlockRows(procs)).await;
if wrote != ExitCode::Success {
return wrote;
}
}
failure.unwrap_or(ExitCode::Success)
}
pub async fn restart(
client: &Client,
streams: &mut Streams<'_>,
paths: &ShepPaths,
args: &SelectorArgs,
) -> ExitCode {
restart_within(client, streams, paths, args, dogs::VERSION_BUDGET).await
}
pub async fn restart_within(
client: &Client,
streams: &mut Streams<'_>,
paths: &ShepPaths,
args: &SelectorArgs,
probe: Duration,
) -> ExitCode {
let selectors = match parse_selectors(streams, &args.selectors) {
Ok(selectors) => selectors,
Err(code) => return code,
};
dogs::warn_of_a_dog_a_restart_would_break(streams, paths, &selectors, probe);
let mut refused: Vec<SheepRefusal> = Vec::new();
let (procs, failure) = request_each(
client,
streams,
&selectors,
Some(START_DEADLINE),
|selector| Request::Restart { selector },
|response| match response {
Response::Restarted {
accepted,
refused: rows,
} => {
refused.extend(rows);
Some(accepted)
}
_ => None,
},
)
.await;
let failed: Vec<String> = procs
.iter()
.filter(|info| info.status == shep_core::status::ProcStatus::Errored)
.map(|info| info.name.clone())
.collect();
let carried = (!procs.is_empty() || failure.is_none()) && failed.is_empty();
if carried {
let wrote = render_partial(client, streams, "restart", procs, &refused).await;
if wrote != ExitCode::Success {
return wrote;
}
}
if !failed.is_empty() {
let names = failed.join(", ");
let mut message = format!(
"{names} did not come back up; see `shep bleats {}` or its log files for why",
failed[0]
);
if let Some(refusal) = refused_line("restart", &refused) {
message.push_str("; ");
message.push_str(&refusal);
}
return streams.fail(ExitCode::SpawnFailed, &message);
}
let refusal = refused_line("restart", &refused).map(|message| {
if streams.fmt == Format::Json && carried {
WALK_REFUSED_EXIT
} else {
streams.fail(WALK_REFUSED_EXIT, &message)
}
});
failure.or(refusal).unwrap_or(ExitCode::Success)
}
pub async fn reload(client: &Client, streams: &mut Streams<'_>, args: &SelectorArgs) -> ExitCode {
let selectors = match parse_selectors(streams, &args.selectors) {
Ok(selectors) => selectors,
Err(code) => return code,
};
let mut refused: Vec<SheepRefusal> = Vec::new();
let (procs, failure) = request_each(
client,
streams,
&selectors,
Some(RELOAD_DEADLINE),
|selector| Request::Reload { selector },
|response| match response {
Response::Reloading {
accepted,
refused: rows,
} => {
refused.extend(rows);
Some(accepted)
}
_ => None,
},
)
.await;
let carried = !procs.is_empty() || failure.is_none();
if carried {
let wrote = render_partial(client, streams, "reload", procs, &refused).await;
if wrote != ExitCode::Success {
return wrote;
}
}
let refusal = refused_line("reload", &refused).map(|message| {
if streams.fmt == Format::Json && carried {
WALK_REFUSED_EXIT
} else {
streams.fail(WALK_REFUSED_EXIT, &message)
}
});
failure.or(refusal).unwrap_or(ExitCode::Success)
}
async fn render_partial(
client: &Client,
streams: &mut Streams<'_>,
command: &str,
accepted: Vec<ProcessInfo>,
refused: &[SheepRefusal],
) -> ExitCode {
if streams.fmt == Format::Json {
return write_outcome(emit_partial(
&mut *streams.out,
command,
FlockRows(accepted),
refused,
));
}
render_outcome(client, streams, command, FlockRows(accepted)).await
}
const WALK_REFUSED_EXIT: ExitCode = ExitCode::Failure;
pub(crate) fn refused_line(verb: &str, refused: &[SheepRefusal]) -> Option<String> {
if refused.is_empty() {
return None;
}
let listed: Vec<String> = refused
.iter()
.map(|sheep| format!("{}: {}", sheep.name, sheep.reason))
.collect();
Some(format!("did not {verb}: {}", listed.join("; ")))
}
pub async fn delete_sheep(
client: &Client,
streams: &mut Streams<'_>,
args: &SelectorArgs,
) -> ExitCode {
let selectors = match parse_selectors(streams, &args.selectors) {
Ok(selectors) => selectors,
Err(code) => return code,
};
let (ids, failure) = request_each(
client,
streams,
&selectors,
None,
|selector| Request::Delete { selector },
|response| match response {
Response::Deleted(ids) => Some(ids),
_ => None,
},
)
.await;
if !ids.is_empty() || failure.is_none() {
let count = ids.len();
let listed = ids
.iter()
.map(u32::to_string)
.collect::<Vec<String>>()
.join(", ");
if count > 0 && streams.fmt != Format::Json {
let message = match count {
1 => format!("deleted 1 sheep, id {listed}"),
n => format!("deleted {n} sheep, ids {listed}"),
};
streams.aside("delete", &message);
}
let wrote = render_outcome(client, streams, "delete", DeletedIds(ids)).await;
if wrote != ExitCode::Success {
return wrote;
}
}
failure.unwrap_or(ExitCode::Success)
}
pub async fn stock(client: &Client, streams: &mut Streams<'_>, args: &StockArgs) -> ExitCode {
let body = Request::Scale {
name: args.name.clone(),
count: args.count,
};
let rows =
request_payload(
client,
streams,
body,
Some(START_DEADLINE),
|response| match response {
Response::Scaled(procs) => Some(FlockRows(procs)),
_ => None,
},
)
.await;
match rows {
Ok(rows) => render_outcome(client, streams, "stock", rows).await,
Err(code) => code,
}
}