use aion_proto::generated;
use anyhow::Result;
use serde_json::{Value, json};
use crate::deploy::{DeployTarget, deploy_status_error};
pub(crate) async fn status(target: &DeployTarget) -> Result<Value> {
let mut client = target.client().await?;
let response = client
.list_managed_workers(target.request(generated::ListManagedWorkersRequest {})?)
.await
.map_err(|status| deploy_status_error(&status))?
.into_inner();
let workers: Vec<Value> = response.workers.iter().map(worker_json).collect();
Ok(json!({
"commissioned": response.commissioned,
"remedy": response.remedy,
"workers": workers,
"undecodable": response.undecodable,
}))
}
pub(crate) async fn start(target: &DeployTarget, name: &str) -> Result<Value> {
let mut client = target.client().await?;
let response = client
.start_managed_worker(target.request(request(name))?)
.await
.map_err(|status| deploy_status_error(&status))?
.into_inner();
Ok(one(&response, name))
}
pub(crate) async fn stop(target: &DeployTarget, name: &str) -> Result<Value> {
let mut client = target.client().await?;
let response = client
.stop_managed_worker(target.request(request(name))?)
.await
.map_err(|status| deploy_status_error(&status))?
.into_inner();
Ok(one(&response, name))
}
pub(crate) async fn restart(target: &DeployTarget, name: &str) -> Result<Value> {
let mut client = target.client().await?;
let response = client
.restart_managed_worker(target.request(request(name))?)
.await
.map_err(|status| deploy_status_error(&status))?
.into_inner();
Ok(one(&response, name))
}
fn request(name: &str) -> generated::ManagedWorkerRequest {
generated::ManagedWorkerRequest {
name: name.to_owned(),
}
}
fn one(response: &generated::ManagedWorkerResponse, name: &str) -> Value {
response.worker.as_ref().map_or_else(
|| json!({ "name": name, "error": "the server returned no worker for this deployment" }),
worker_json,
)
}
fn worker_json(worker: &generated::ManagedWorker) -> Value {
json!({
"name": worker.name,
"task_queue": worker.task_queue,
"desired": worker.desired,
"state": worker.state,
"pid": worker.pid,
"process_group": worker.process_group,
"restarts": worker.restarts,
"last_exit": last_exit(worker),
"last_error": worker.last_error,
"deployed_content_hash": worker.deployed_content_hash,
"spawn_content_hash": worker.spawn_content_hash,
"spawn_path": worker.spawn_path,
"running_upgraded_binary": upgraded(worker),
})
}
fn upgraded(worker: &generated::ManagedWorker) -> Value {
worker
.spawn_content_hash
.as_ref()
.map_or(Value::Null, |spawned| {
Value::Bool(*spawned != worker.deployed_content_hash)
})
}
fn last_exit(worker: &generated::ManagedWorker) -> Value {
worker.last_exit_at.as_ref().map_or(Value::Null, |at| {
json!({
"at": at,
"code": worker.last_exit_code,
"signal": worker.last_exit_signal,
"requested": worker.last_exit_requested,
})
})
}
#[cfg(test)]
mod tests {
use aion_proto::generated;
use super::{one, upgraded, worker_json};
fn worker() -> generated::ManagedWorker {
generated::ManagedWorker {
name: "shells".to_owned(),
task_queue: "shell".to_owned(),
desired: "running".to_owned(),
state: "running".to_owned(),
pid: Some(11),
process_group: Some(11),
restarts: 0,
last_exit_at: None,
last_exit_code: None,
last_exit_signal: None,
last_exit_requested: false,
last_error: None,
deployed_content_hash: "aaaa".to_owned(),
spawn_content_hash: Some("aaaa".to_owned()),
spawn_path: Some("/usr/local/bin/aion".to_owned()),
}
}
#[test]
fn the_upgrade_flag_distinguishes_unknown_from_not_upgraded() {
assert_eq!(upgraded(&worker()), serde_json::Value::Bool(false));
let mut upgraded_worker = worker();
upgraded_worker.spawn_content_hash = Some("bbbb".to_owned());
assert_eq!(upgraded(&upgraded_worker), serde_json::Value::Bool(true));
let mut never_run = worker();
never_run.spawn_content_hash = None;
assert_eq!(upgraded(&never_run), serde_json::Value::Null);
}
#[test]
fn desired_and_actual_state_are_both_rendered() {
let mut stalled = worker();
stalled.state = "stopped".to_owned();
let rendered = worker_json(&stalled);
assert_eq!(rendered["desired"], "running");
assert_eq!(rendered["state"], "stopped");
}
#[test]
fn a_response_without_a_worker_says_so_by_name() {
let rendered = one(&generated::ManagedWorkerResponse { worker: None }, "shells");
assert_eq!(rendered["name"], "shells");
assert!(rendered["error"].is_string());
}
#[test]
fn an_exit_renders_every_recorded_field() {
let mut exited = worker();
exited.last_exit_at = Some("2026-08-09T00:00:00Z".to_owned());
exited.last_exit_code = Some(3);
exited.last_exit_requested = false;
let rendered = worker_json(&exited);
assert_eq!(rendered["last_exit"]["code"], 3);
assert_eq!(rendered["last_exit"]["requested"], false);
assert_eq!(rendered["last_exit"]["at"], "2026-08-09T00:00:00Z");
}
}