Skip to content

Commit f93cff3

Browse files
committed
refactor(cli): remove status/stop/reset commands
Why: - simplify the CLI surface as requested - avoid unsupported controls outside the UI Impact: - removes status, stop, and reset subcommands - Tests: just check
1 parent 64a5a93 commit f93cff3

2 files changed

Lines changed: 1 addition & 226 deletions

File tree

crates/intar-cli/src/commands.rs

Lines changed: 1 addition & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@ use crate::agent::{AGENT_AARCH64, AGENT_X86_64, is_placeholder};
22
use anyhow::{Context, Result, bail};
33
use intar_core::Scenario;
44
use intar_ui::App;
5-
use intar_vm::{IntarDirs, RunState};
6-
use std::io::{BufRead, BufReader, Write};
7-
use std::os::unix::net::UnixStream;
5+
use intar_vm::IntarDirs;
86
use std::path::{Path, PathBuf};
9-
use std::process::Command;
10-
use std::time::{Duration, Instant};
117

128
pub async fn start(scenario_path: PathBuf) -> Result<()> {
139
if is_placeholder(AGENT_X86_64) || is_placeholder(AGENT_AARCH64) {
@@ -32,31 +28,6 @@ pub async fn start(scenario_path: PathBuf) -> Result<()> {
3228
Ok(())
3329
}
3430

35-
pub fn status() -> Result<()> {
36-
let dirs = IntarDirs::new().context("Failed to initialize directories")?;
37-
let runs_dir = dirs.runs_dir();
38-
39-
if !runs_dir.exists() {
40-
println!("No scenario runs found.");
41-
return Ok(());
42-
}
43-
44-
println!("Scenario runs in {}:", runs_dir.display());
45-
for entry in std::fs::read_dir(&runs_dir)? {
46-
let entry = entry?;
47-
if entry.file_type()?.is_dir() {
48-
let name = entry.file_name();
49-
let has_qmp_sock = std::fs::read_dir(entry.path())?
50-
.filter_map(Result::ok)
51-
.any(|e| e.path().extension().is_some_and(|ext| ext == "sock"));
52-
let status = if has_qmp_sock { "running" } else { "stopped" };
53-
println!(" {} ({})", name.to_string_lossy(), status);
54-
}
55-
}
56-
57-
Ok(())
58-
}
59-
6031
pub fn ssh(vm_name: &str, run_name: Option<&str>) -> Result<()> {
6132
let dirs = IntarDirs::new().context("Failed to initialize directories")?;
6233
let runs_root = dirs.runs_dir();
@@ -137,38 +108,6 @@ pub fn ssh(vm_name: &str, run_name: Option<&str>) -> Result<()> {
137108
Ok(())
138109
}
139110

140-
pub fn reset() {
141-
println!("Reset must be done from within the running UI (press 'r')");
142-
}
143-
144-
pub fn stop() -> Result<()> {
145-
let dirs = IntarDirs::new().context("Failed to initialize directories")?;
146-
let runs_root = dirs.runs_dir();
147-
148-
if !runs_root.exists() {
149-
println!("No scenario runs found.");
150-
return Ok(());
151-
}
152-
153-
let run_dirs: Vec<PathBuf> = std::fs::read_dir(&runs_root)?
154-
.filter_map(Result::ok)
155-
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
156-
.map(|e| e.path())
157-
.collect();
158-
159-
if run_dirs.is_empty() {
160-
println!("No scenario runs found.");
161-
return Ok(());
162-
}
163-
164-
for run_dir in run_dirs {
165-
stop_run_dir(&run_dir)?;
166-
println!("Stopped and cleaned run at {}", run_dir.display());
167-
}
168-
169-
Ok(())
170-
}
171-
172111
pub fn list(dir: &Path) -> Result<()> {
173112
println!("Searching for scenarios in: {}", dir.display());
174113

@@ -198,155 +137,6 @@ pub fn list(dir: &Path) -> Result<()> {
198137
Ok(())
199138
}
200139

201-
fn stop_run_dir(run_dir: &Path) -> Result<()> {
202-
let state = RunState::load(run_dir);
203-
let mut vms_failed_to_stop = Vec::new();
204-
205-
if let Ok(state) = state {
206-
for vm in state.vms {
207-
let qmp_socket = run_dir.join(format!("{}-qmp.sock", vm.name));
208-
let pid_file = run_dir.join(format!("{}-qemu.pid", vm.name));
209-
210-
if qmp_socket.exists()
211-
&& let Err(e) = send_qmp_quit(&qmp_socket)
212-
{
213-
eprintln!(
214-
"Warning: failed to stop VM '{}' via {}: {}",
215-
vm.name,
216-
qmp_socket.display(),
217-
e
218-
);
219-
}
220-
221-
if let Some(pid) = read_pid_file(&pid_file) {
222-
if let Err(e) = wait_for_pid_exit(pid, Duration::from_secs(3)) {
223-
eprintln!(
224-
"Warning: failed waiting for VM '{}' (pid {pid}) to exit: {e}",
225-
vm.name
226-
);
227-
}
228-
229-
if pid_is_running(pid) {
230-
if let Err(e) = send_kill_signal(pid, "-TERM") {
231-
eprintln!("Warning: failed to send SIGTERM to pid {pid}: {e}");
232-
}
233-
let _ = wait_for_pid_exit(pid, Duration::from_secs(2));
234-
}
235-
236-
if pid_is_running(pid) {
237-
if let Err(e) = send_kill_signal(pid, "-KILL") {
238-
eprintln!("Warning: failed to send SIGKILL to pid {pid}: {e}");
239-
}
240-
let _ = wait_for_pid_exit(pid, Duration::from_secs(2));
241-
}
242-
243-
if pid_is_running(pid) {
244-
vms_failed_to_stop.push(vm.name);
245-
}
246-
}
247-
}
248-
} else if let Err(e) = state {
249-
eprintln!(
250-
"Warning: failed to load state for {}: {}",
251-
run_dir.display(),
252-
e
253-
);
254-
}
255-
256-
if !vms_failed_to_stop.is_empty() {
257-
bail!(
258-
"Failed to stop VMs in {}: {}",
259-
run_dir.display(),
260-
vms_failed_to_stop.join(", ")
261-
);
262-
}
263-
264-
remove_dir_all_with_retries(run_dir, 5, Duration::from_millis(200))
265-
.with_context(|| format!("Failed to delete {}", run_dir.display()))?;
266-
267-
Ok(())
268-
}
269-
270-
fn send_qmp_quit(socket: &Path) -> std::io::Result<()> {
271-
let mut stream = UnixStream::connect(socket)?;
272-
stream.set_read_timeout(Some(Duration::from_secs(1)))?;
273-
stream.set_write_timeout(Some(Duration::from_secs(1)))?;
274-
275-
let mut reader = BufReader::new(stream.try_clone()?);
276-
let mut line = String::new();
277-
278-
// QMP greeting
279-
let _ = reader.read_line(&mut line);
280-
281-
stream.write_all(br#"{"execute": "qmp_capabilities"}\n"#)?;
282-
line.clear();
283-
let _ = reader.read_line(&mut line);
284-
285-
stream.write_all(br#"{"execute": "quit"}\n"#)?;
286-
287-
Ok(())
288-
}
289-
290-
fn read_pid_file(path: &Path) -> Option<u32> {
291-
let raw = std::fs::read_to_string(path).ok()?;
292-
let trimmed = raw.trim();
293-
if trimmed.is_empty() {
294-
return None;
295-
}
296-
trimmed.parse::<u32>().ok()
297-
}
298-
299-
fn pid_is_running(pid: u32) -> bool {
300-
Command::new("kill")
301-
.args(["-0", &pid.to_string()])
302-
.status()
303-
.is_ok_and(|s| s.success())
304-
}
305-
306-
fn send_kill_signal(pid: u32, signal: &str) -> std::io::Result<()> {
307-
let status = Command::new("kill")
308-
.args([signal, &pid.to_string()])
309-
.status()?;
310-
if status.success() {
311-
return Ok(());
312-
}
313-
Err(std::io::Error::other(format!(
314-
"kill {signal} {pid} failed with status {status}"
315-
)))
316-
}
317-
318-
fn wait_for_pid_exit(pid: u32, timeout: Duration) -> std::io::Result<()> {
319-
let deadline = Instant::now() + timeout;
320-
while Instant::now() < deadline {
321-
if !pid_is_running(pid) {
322-
return Ok(());
323-
}
324-
std::thread::sleep(Duration::from_millis(100));
325-
}
326-
Err(std::io::Error::other(
327-
"timed out waiting for process to exit",
328-
))
329-
}
330-
331-
fn remove_dir_all_with_retries(
332-
path: &Path,
333-
attempts: usize,
334-
delay: Duration,
335-
) -> std::io::Result<()> {
336-
for i in 0..attempts {
337-
match std::fs::remove_dir_all(path) {
338-
Ok(()) => return Ok(()),
339-
Err(e) => {
340-
if i + 1 == attempts {
341-
return Err(e);
342-
}
343-
std::thread::sleep(delay);
344-
}
345-
}
346-
}
347-
Ok(())
348-
}
349-
350140
pub fn logs(run_name: Option<&str>, vm_name: Option<&str>, log_type: &str) -> Result<()> {
351141
let dirs = IntarDirs::new().context("Failed to initialize directories")?;
352142
let runs_root = dirs.runs_dir();

crates/intar-cli/src/main.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,13 @@ enum Commands {
2424
scenario: PathBuf,
2525
},
2626
/// Show status of running scenario
27-
Status,
28-
/// SSH into a VM
2927
Ssh {
3028
/// Name of the VM
3129
vm_name: String,
3230
/// Name of the run (defaults to most recent)
3331
#[arg(short, long)]
3432
run: Option<String>,
3533
},
36-
/// Reset scenario to initial state
37-
Reset,
38-
/// Stop the running scenario
39-
Stop,
4034
/// List available scenarios
4135
List {
4236
/// Directory to search for scenarios
@@ -83,18 +77,9 @@ async fn main() -> anyhow::Result<()> {
8377
Commands::Start { scenario } => {
8478
commands::start(scenario).await?;
8579
}
86-
Commands::Status => {
87-
commands::status()?;
88-
}
8980
Commands::Ssh { vm_name, run } => {
9081
commands::ssh(&vm_name, run.as_deref())?;
9182
}
92-
Commands::Reset => {
93-
commands::reset();
94-
}
95-
Commands::Stop => {
96-
commands::stop()?;
97-
}
9883
Commands::List { dir } => {
9984
commands::list(&dir)?;
10085
}

0 commit comments

Comments
 (0)