clusterflux-public/crates/disasmer-cli/src/process.rs
Disasmer release dry run 2bef715211 Public dry run dryrun-20b59dc46b72
Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19

Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
2026-07-14 10:08:15 +02:00

333 lines
11 KiB
Rust

use anyhow::Result;
use serde_json::{json, Value};
use crate::client::{
authenticated_or_local_trusted_request, list_task_events_if_available_with_session,
stored_session_for_coordinator, JsonLineSession,
};
use crate::config::StoredCliSession;
use crate::process_events::{
process_cancel_request_summary, process_restart_request_summary, process_state_from_tasks,
task_summaries,
};
use crate::{
confirmation_required_report, CliScopeArgs, ProcessAbortArgs, ProcessCancelArgs,
ProcessListArgs, ProcessRestartArgs, ProcessStatusArgs,
};
fn hydrate_process_scope(scope: &mut CliScopeArgs, stored_session: Option<&StoredCliSession>) {
if scope.coordinator.is_none() {
scope.coordinator = stored_session
.filter(|session| session.session_secret.is_some())
.map(|session| session.coordinator.clone());
}
if let Some(bound_session) = scope
.coordinator
.as_deref()
.and_then(|coordinator| stored_session_for_coordinator(coordinator, stored_session))
{
scope.tenant = bound_session.tenant.clone();
scope.project = bound_session.project.clone();
scope.user = bound_session.user.clone();
}
}
pub(crate) fn process_list_report_with_session(
mut args: ProcessListArgs,
stored_session: Option<&StoredCliSession>,
) -> Result<Value> {
hydrate_process_scope(&mut args.scope, stored_session);
let Some(coordinator) = &args.scope.coordinator else {
return Ok(json!({
"command": "process list",
"status": "requires_coordinator",
"processes": [],
}));
};
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(authenticated_or_local_trusted_request(
coordinator,
stored_session,
json!({ "type": "list_processes" }),
json!({
"type": "list_processes",
"tenant": args.scope.tenant,
"project": args.scope.project,
"actor_user": args.scope.user,
}),
)?)?;
let processes = response
.get("processes")
.cloned()
.unwrap_or_else(|| json!([]));
Ok(json!({
"command": "process list",
"status": "ok",
"coordinator": coordinator,
"tenant": args.scope.tenant,
"project": args.scope.project,
"user": args.scope.user,
"processes": processes,
"response": response,
"coordinator_session_requests": session.requests(),
}))
}
#[cfg(test)]
pub(crate) fn process_status_report(args: ProcessStatusArgs) -> Result<Value> {
process_status_report_with_session(args, None)
}
pub(crate) fn process_status_report_with_session(
mut args: ProcessStatusArgs,
stored_session: Option<&StoredCliSession>,
) -> Result<Value> {
hydrate_process_scope(&mut args.scope, stored_session);
let live = process_list_report_with_session(
ProcessListArgs {
scope: args.scope.clone(),
},
stored_session,
)?;
let live_process = live
.get("processes")
.and_then(Value::as_array)
.and_then(|processes| {
processes.iter().find(|process| {
process.get("process").and_then(Value::as_str) == Some(args.process.as_str())
})
})
.cloned();
let events = list_task_events_if_available_with_session(
args.scope.coordinator.as_deref(),
&args.scope,
Some(args.process.clone()),
stored_session,
)?;
let current_tasks = task_summaries(events.as_ref());
let current_task_count = current_tasks.as_array().map(Vec::len).unwrap_or(0);
let historical_state = process_state_from_tasks(events.as_ref());
let state = live_process
.as_ref()
.and_then(|process| process.get("state"))
.and_then(Value::as_str)
.map(str::to_owned)
.unwrap_or_else(|| {
if live.get("status").and_then(Value::as_str) == Some("ok") {
"not_active".to_owned()
} else if events.is_some() {
historical_state.to_owned()
} else {
"unknown_without_coordinator".to_owned()
}
});
Ok(json!({
"command": "process status",
"process": args.process,
"state": state,
"live_process": live_process,
"started_entrypoint": "unknown_from_task_events",
"current_task_count": current_task_count,
"current_tasks": current_tasks,
"debug_state": {
"frozen": null,
"status": "unknown_from_cli_task_events"
},
"events": events,
}))
}
#[cfg(test)]
pub(crate) fn process_restart_report(args: ProcessRestartArgs) -> Result<Value> {
process_restart_report_with_session(args, None)
}
pub(crate) fn process_restart_report_with_session(
mut args: ProcessRestartArgs,
stored_session: Option<&StoredCliSession>,
) -> Result<Value> {
hydrate_process_scope(&mut args.scope, stored_session);
if !args.yes {
return Ok(confirmation_required_report(
"process restart",
"restart_virtual_process",
json!({
"coordinator": args.scope.coordinator,
"tenant": args.scope.tenant,
"project": args.scope.project,
"process": args.process,
}),
format!("disasmer process restart --process {} --yes", args.process),
));
}
if let Some(coordinator) = &args.scope.coordinator {
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(authenticated_or_local_trusted_request(
coordinator,
stored_session,
json!({
"type": "start_process",
"process": args.process,
"restart": true,
}),
json!({
"type": "start_process",
"tenant": args.scope.tenant,
"project": args.scope.project,
"process": args.process,
"restart": true,
}),
)?)?;
let restart_request = process_restart_request_summary(&response, !args.yes);
return Ok(json!({
"command": "process restart",
"coordinator": coordinator,
"process": args.process,
"requires_confirmation": !args.yes,
"restart_request": restart_request,
"response": response,
"coordinator_session_requests": session.requests(),
}));
}
Ok(json!({
"command": "process restart",
"status": "requires_coordinator",
"requires_confirmation": !args.yes,
"process": args.process,
"restart_request": {
"status": "requires_coordinator",
"operation": "restart_virtual_process",
"explicit_user_action": true,
},
}))
}
pub(crate) fn process_cancel_report(args: ProcessCancelArgs) -> Result<Value> {
process_cancel_report_with_session(args, None)
}
pub(crate) fn process_cancel_report_with_session(
mut args: ProcessCancelArgs,
stored_session: Option<&StoredCliSession>,
) -> Result<Value> {
hydrate_process_scope(&mut args.scope, stored_session);
if !args.yes {
return Ok(confirmation_required_report(
"process cancel",
"cancel_virtual_process",
json!({
"coordinator": args.scope.coordinator,
"tenant": args.scope.tenant,
"project": args.scope.project,
"process": args.process,
"node": args.node,
"task": args.task,
}),
format!("disasmer process cancel --process {} --yes", args.process),
));
}
if let Some(coordinator) = &args.scope.coordinator {
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(authenticated_or_local_trusted_request(
coordinator,
stored_session,
json!({
"type": "cancel_process",
"process": args.process,
}),
json!({
"type": "cancel_process",
"tenant": args.scope.tenant,
"project": args.scope.project,
"actor_user": args.scope.user,
"process": args.process,
}),
)?)?;
let cancel_request = process_cancel_request_summary(&response, !args.yes);
return Ok(json!({
"command": "process cancel",
"coordinator": coordinator,
"requires_confirmation": !args.yes,
"process": args.process,
"node": args.node,
"task": args.task,
"cancel_request": cancel_request,
"response": response,
"coordinator_session_requests": session.requests(),
}));
}
Ok(json!({
"command": "process cancel",
"status": "requires_coordinator",
"requires_confirmation": !args.yes,
"process": args.process,
"node": args.node,
"task": args.task,
"cancel_request": {
"status": "requires_coordinator",
"operation": "cancel_virtual_process",
"whole_process_cancel_available": true,
"explicit_user_action": true,
},
}))
}
pub(crate) fn process_abort_report_with_session(
mut args: ProcessAbortArgs,
stored_session: Option<&StoredCliSession>,
) -> Result<Value> {
hydrate_process_scope(&mut args.scope, stored_session);
if !args.yes {
return Ok(confirmation_required_report(
"process abort",
"abort_virtual_process",
json!({
"coordinator": args.scope.coordinator,
"tenant": args.scope.tenant,
"project": args.scope.project,
"process": args.process,
}),
format!("disasmer process abort --process {} --yes", args.process),
));
}
let Some(coordinator) = &args.scope.coordinator else {
return Ok(json!({
"command": "process abort",
"status": "requires_coordinator",
"process": args.process,
"requires_confirmation": false,
}));
};
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(authenticated_or_local_trusted_request(
coordinator,
stored_session,
json!({
"type": "abort_process",
"process": args.process,
}),
json!({
"type": "abort_process",
"tenant": args.scope.tenant,
"project": args.scope.project,
"actor_user": args.scope.user,
"process": args.process,
}),
)?)?;
Ok(json!({
"command": "process abort",
"status": "aborted",
"coordinator": coordinator,
"process": args.process,
"requires_confirmation": false,
"abort_request": {
"accepted": response.get("type").and_then(Value::as_str) == Some("process_aborted"),
"operation": "abort_virtual_process",
"forced": true,
"cooperative": false,
"process_slot_released": true,
},
"response": response,
"coordinator_session_requests": session.requests(),
}))
}