use super::flock_display::flock;
use super::secret_inspection::describe;
use crate::cli::{Format, SelectorArgs};
use crate::exit::ExitCode;
use crate::output::Streams;
use crate::remote::Link;
use shep_client::Client;
use shep_client::testing::{fake_client_answering, fake_client_with_ack, sample_ack, sample_info};
use shep_core::paths::ShepPaths;
use shep_core::protocol::DogSource;
use shep_core::protocol::{HostUsage, ProcessInfo, Request, Response};
use shep_core::secrets::{self};
use shep_core::status::ProcStatus;
use shep_daemon::snapshot::FlockSnapshot;
use std::time::Duration;
pub(super) const RECV_TIMEOUT: Duration = Duration::from_secs(5);
pub(super) fn sample_host() -> HostUsage {
HostUsage {
cpu_percent: Some(11.459_433),
memory_used_bytes: 39_963_869_184,
memory_total_bytes: 51_539_607_552,
disk_bytes_per_second: Some((1_258_291, 491_520)),
network_bytes_per_second: Some((24_594, 9_260)),
}
}
pub(super) async fn shepherd_answering(
path: &std::path::Path,
host: Option<HostUsage>,
) -> (
Client,
tokio::sync::mpsc::UnboundedReceiver<shep_core::protocol::Envelope>,
) {
fake_client_answering(path, move |request| match request {
Request::HostUsage => Response::HostUsage(host),
_ => Response::Flock(vec![sample_info()]),
})
.await
}
pub(super) async fn listing(client: &Client, fmt: Format) -> (ExitCode, String) {
let mut out = Vec::new();
let mut err = Vec::new();
let code = {
let mut streams = Streams::bare(&mut out, &mut err, fmt);
flock(client, &mut streams).await
};
(code, String::from_utf8(out).unwrap())
}
pub(super) struct Described {
pub(super) code: ExitCode,
pub(super) paths: ShepPaths,
pub(super) out: Vec<u8>,
pub(super) err: Vec<u8>,
}
pub(super) fn app_with_env(name: &str, env: &[(&str, &str)]) -> shep_core::config::AppConfig {
let mut config = shep_core::config::AppConfig::minimal(name, "./srv");
for (key, value) in env {
config.env.insert((*key).into(), (*value).into());
}
config
}
pub(super) async fn describe_against_a_local_roll(
apps: Vec<shep_core::config::AppConfig>,
after_roll: impl FnOnce(&ShepPaths),
fmt: Format,
) -> Described {
let dir = tempfile::tempdir().unwrap();
let path = shep_client::testing::control_address(dir.path());
let (client, daemon) = fake_client_with_ack(&path, sample_ack()).await;
daemon.reply_to_describe(
apps.iter()
.zip(1..)
.map(|(app, id)| ProcessInfo::builder(id, &app.name, ProcStatus::Online).build())
.collect(),
);
let home = dir.path().display().to_string();
let paths = ShepPaths::resolve(
&move |key| (key == "SHEP_HOME").then(|| home.clone()),
dir.path(),
);
let roll = FlockSnapshot::with_apps(
apps.into_iter()
.map(|app| shep_daemon::snapshot::SavedApp {
app,
instances_running: 1,
disabled: false,
})
.collect(),
);
std::fs::write(&paths.snapshot, serde_json::to_vec(&roll).unwrap()).unwrap();
after_roll(&paths);
let mut out = Vec::new();
let mut err = Vec::new();
let code = {
let mut streams = Streams::bare(&mut out, &mut err, fmt);
describe(
&client,
&mut streams,
&Link::Local(&paths),
&SelectorArgs {
selectors: vec!["all".into()],
},
)
.await
};
Described {
code,
paths,
out,
err,
}
}
pub(super) async fn describe_with_a_seeded_web(
fmt: Format,
) -> (ExitCode, shep_core::paths::ShepPaths, Vec<u8>) {
let web = app_with_env(
"web",
&[
("A", "{{secret:DB_PASSWORD}}"),
("B", "{{secret:vercel/API_KEY}}"),
],
);
let described = describe_against_a_local_roll(
vec![web],
|paths| secrets::set(&paths.secrets, "DB_PASSWORD", "production", "hunter2").unwrap(),
fmt,
)
.await;
(described.code, described.paths, described.out)
}
pub(super) fn out_contains(value: &serde_json::Value, needle: &str) -> bool {
value.to_string().contains(needle)
}
pub(super) fn sheep(id: u32, status: ProcStatus) -> ProcessInfo {
ProcessInfo::builder(id, format!("s{id}"), status).build()
}
pub(super) fn dog(id: u32) -> ProcessInfo {
ProcessInfo::builder(id, format!("d{id}"), ProcStatus::Online)
.dog(Some(DogSource::BuiltIn {
grants: None,
events: Vec::new(),
}))
.build()
}