1use crate::commands::process::{kill_process, wait_for_exit};
2use crate::commands::runtime_state::{RuntimeStateStore, RuntimeStatus, resolve_hyper_dir};
3use crate::core::{Command, CommandContext, CommandResult, ComponentType};
4use crate::error::ActrCliError;
5use anyhow::Result;
6use async_trait::async_trait;
7use clap::Args;
8use std::path::PathBuf;
9use std::time::Duration;
10
11#[derive(Args, Debug)]
12pub struct RmCommand {
13 #[arg(value_name = "WID")]
15 pub wid: String,
16
17 #[arg(short = 'c', long = "config", value_name = "FILE")]
19 pub config: Option<PathBuf>,
20
21 #[arg(long = "hyper-dir", value_name = "DIR")]
23 pub hyper_dir: Option<PathBuf>,
24
25 #[arg(short = 'f', long = "force")]
27 pub force: bool,
28}
29
30#[async_trait]
31impl Command for RmCommand {
32 async fn execute(&self, _ctx: &CommandContext) -> Result<CommandResult> {
33 let hyper_dir = resolve_hyper_dir(self.config.as_deref(), self.hyper_dir.as_deref())?;
34 let store = RuntimeStateStore::new(hyper_dir);
35 let entry = store.resolve_wid_prefix(&self.wid).await?;
36
37 if entry.status == RuntimeStatus::Running {
38 if !self.force {
39 return Err(ActrCliError::command_error(format!(
40 "Workload {} is running. Stop it first or use -f to force remove.",
41 entry.wid_short()
42 ))
43 .into());
44 }
45
46 if kill_process(entry.record.pid)?
47 && !wait_for_exit(entry.record.pid, Duration::from_secs(1)).await
48 {
49 return Err(ActrCliError::command_error(format!(
50 "Process {} did not exit after SIGKILL",
51 entry.record.pid
52 ))
53 .into());
54 }
55 }
56
57 store.delete_record_by_wid(&entry.record.wid).await?;
58 println!("Removed {}", entry.wid_short());
59 Ok(CommandResult::Success(String::new()))
60 }
61
62 fn required_components(&self) -> Vec<ComponentType> {
63 vec![]
64 }
65
66 fn name(&self) -> &str {
67 "rm"
68 }
69
70 fn description(&self) -> &str {
71 "Remove a detached runtime instance record"
72 }
73}