use super::*;
fn build_remote_ops_source(
endpoint: String,
timeout_ms: u64,
gateway_observation: Option<serde_json::Value>,
) -> OpsSource {
OpsSource {
label: format!("remote:{endpoint}"),
json: serde_json::json!({
"mode": "remote_service",
"service": STATUS_SERVICE_NAME,
"endpoint": endpoint,
"timeout_ms": timeout_ms,
"gateway_observation": gateway_observation,
}),
}
}
fn build_report_ops_source(report: String) -> OpsSource {
OpsSource {
label: format!("report:{report}"),
json: serde_json::json!({
"mode": "report_file",
"report": report,
}),
}
}
fn load_report_snapshot(report: String, refresh_demo: bool) -> Result<(StatusSnapshot, OpsSource), String> {
let path = PathBuf::from(&report);
if refresh_demo {
let snapshot = crate::demo::demo_status_snapshot();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|err| {
format!(
"create status snapshot report dir {} failed: {err}",
parent.display()
)
})?;
}
introspection_core::write_status_snapshot(&path, &snapshot).map_err(|err| {
format!(
"write status snapshot to {} failed: {err}",
path.display()
)
})?;
return Ok((snapshot, build_report_ops_source(report)));
}
let snapshot = introspection_core::read_status_snapshot(&path)
.map_err(|err| format!("read status snapshot from {} failed: {err}", path.display()))?;
Ok((snapshot, build_report_ops_source(report)))
}
fn fetch_gateway_observation(endpoint: &str, timeout_ms: u64) -> Option<serde_json::Value> {
let client = crate::gateway::make_udp_service_client(endpoint.to_string(), timeout_ms).ok()?;
let request_id = crate::gateway::next_request_id();
let request = crate::gateway::build_request(crate::gateway::STATUS_OP_GATEWAY_OBSERVE);
let response: crate::gateway::StatusServiceResponse = client
.call_json(crate::gateway::STATUS_SERVICE_NAME, request_id, &request)
.ok()?;
crate::gateway::validate_response(
&response,
request_id,
crate::gateway::STATUS_OP_GATEWAY_OBSERVE,
)
.ok()?;
response.op_result
}
pub(in crate::commands::ops) fn load_status_snapshot(
args: &[String],
) -> Result<(StatusSnapshot, OpsSource), String> {
if let Some(report) = option_value(args, "--report") {
return load_report_snapshot(report, has_flag(args, "--refresh-demo"));
}
crate::commands::snapshot_shared::load_status_snapshot(
args,
option_value(args, "--endpoint"),
"--timeout-ms",
DEFAULT_STATUS_REPORT_PATH,
)
}
pub(in crate::commands::ops) fn load_baseline_snapshot(
args: &[String],
) -> Result<(StatusSnapshot, OpsSource), String> {
if let Some(report) = option_value(args, "--baseline-report") {
return load_report_snapshot(report, has_flag(args, "--refresh-demo"));
}
let raw_endpoint = option_value(args, "--baseline-endpoint").ok_or_else(|| {
String::from("ops diff requires --baseline-endpoint <addr>")
})?;
let timeout_ms = parse_u64_option(args, "--baseline-timeout-ms", 1000)?;
let (snapshot, endpoint) = crate::commands::snapshot_shared::fetch_remote_status_snapshot(
&raw_endpoint,
timeout_ms,
)?;
let gateway_observation = fetch_gateway_observation(&endpoint, timeout_ms);
let source = build_remote_ops_source(endpoint, timeout_ms, gateway_observation);
Ok((snapshot, source))
}
pub(in crate::commands::ops) fn load_fleet_snapshots(
args: &[String],
baseline: bool,
) -> Result<Vec<(StatusSnapshot, OpsSource)>, String> {
let reports_option = if baseline {
"--baseline-reports"
} else {
"--reports"
};
let endpoints_option = if baseline {
"--baseline-endpoints"
} else {
"--endpoints"
};
let timeout_option = if baseline {
"--baseline-timeout-ms"
} else {
"--timeout-ms"
};
let mut items = Vec::new();
let timeout_ms = parse_u64_option(args, timeout_option, 1000)?;
if let Some(raw_reports) = option_value(args, reports_option) {
for report in parse_csv_list(&raw_reports) {
let snapshot = introspection_core::read_status_snapshot(&report).map_err(|err| {
format!("read status snapshot from {report} failed: {err}")
})?;
items.push((snapshot, build_report_ops_source(report)));
}
}
if let Some(raw_endpoints) = option_value(args, endpoints_option) {
for raw in parse_csv_list(&raw_endpoints) {
let (snapshot, endpoint) =
crate::commands::snapshot_shared::fetch_remote_status_snapshot(
&raw,
timeout_ms,
)?;
let gateway_observation = fetch_gateway_observation(&endpoint, timeout_ms);
items.push((
snapshot,
build_remote_ops_source(endpoint, timeout_ms, gateway_observation),
));
}
} else if !baseline && items.is_empty() {
for endpoint in crate::helpers::discover_cluster_endpoints(args) {
let (snapshot, endpoint) =
crate::commands::snapshot_shared::fetch_remote_status_snapshot(
&endpoint,
timeout_ms,
)?;
let gateway_observation = fetch_gateway_observation(&endpoint, timeout_ms);
items.push((
snapshot,
build_remote_ops_source(endpoint, timeout_ms, gateway_observation),
));
}
}
if baseline {
if items.is_empty() && option_value(args, "--baseline-endpoint").is_some() {
let (snapshot, source) = load_baseline_snapshot(args)?;
items.push((snapshot, source));
}
return Ok(items);
}
if items.is_empty() {
let (snapshot, source) = load_status_snapshot(args)?;
items.push((snapshot, source));
}
Ok(items)
}
pub(in crate::commands::ops) fn parse_csv_list(raw: &str) -> Vec<String> {
raw.split(',')
.map(str::trim)
.filter(|item| !item.is_empty())
.map(ToString::to_string)
.collect()
}
pub(in crate::commands::ops) fn write_json_payload_file(
path: &Path,
payload: &serde_json::Value,
) -> Result<(), String> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|err| {
format!(
"create ops diff output dir {} failed: {err}",
parent.display()
)
})?;
}
let body = serde_json::to_string_pretty(payload)
.map_err(|err| format!("serialize ops diff report failed: {err}"))?;
fs::write(path, body)
.map_err(|err| format!("write ops diff report to {} failed: {err}", path.display()))
}