feat: ls only lists running jobs, prune removes all running jobs
All checks were successful
CI / Check, test, lint (push) Successful in 31s
All checks were successful
CI / Check, test, lint (push) Successful in 31s
This commit is contained in:
14
SPEC.md
14
SPEC.md
@@ -66,8 +66,10 @@ Useful for commands that need no local files (e.g. `p -n -- htop`).
|
|||||||
```
|
```
|
||||||
p ls
|
p ls
|
||||||
```
|
```
|
||||||
List jobs across all workers. Shows: ID (short), worker, original CWD,
|
List **running jobs** across all workers. Pass `-a` / `--all` to also show
|
||||||
command, status, duration. Style inspired by `docker ps` / `lxc list`.
|
completed jobs (done, failed, stopped).
|
||||||
|
Shows: ID (short), worker, original CWD, command, status, duration.
|
||||||
|
Style inspired by `docker ps` / `lxc list`.
|
||||||
|
|
||||||
```
|
```
|
||||||
p attach <job-id>
|
p attach <job-id>
|
||||||
@@ -100,6 +102,14 @@ p rm <job-id>
|
|||||||
Remove a job record and its remote work directory. Refuses to remove a
|
Remove a job record and its remote work directory. Refuses to remove a
|
||||||
running job without `--force`.
|
running job without `--force`.
|
||||||
|
|
||||||
|
```
|
||||||
|
p prune
|
||||||
|
```
|
||||||
|
Remove all finished job records (status: done, failed, stopped) and their
|
||||||
|
remote work directories. Jobs with status `running` or `unknown` are left
|
||||||
|
untouched. Pass `--force` to also include `unknown` jobs.
|
||||||
|
Pass `--dry-run` to preview what would be removed without deleting anything.
|
||||||
|
|
||||||
### Worker management
|
### Worker management
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
18
p/src/cli.rs
18
p/src/cli.rs
@@ -23,8 +23,12 @@ pub struct Cli {
|
|||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// List all jobs across all workers
|
/// List running jobs (pass --all to include finished jobs)
|
||||||
Ls,
|
Ls {
|
||||||
|
/// Show all jobs, not just running ones
|
||||||
|
#[arg(short, long)]
|
||||||
|
all: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Re-attach to the tmux session of a running job
|
/// Re-attach to the tmux session of a running job
|
||||||
Attach {
|
Attach {
|
||||||
@@ -66,6 +70,16 @@ pub enum Command {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Remove all finished job records and their remote work directories
|
||||||
|
Prune {
|
||||||
|
/// Also remove jobs with unknown status (worker was unreachable)
|
||||||
|
#[arg(short, long)]
|
||||||
|
force: bool,
|
||||||
|
/// Preview what would be removed without deleting anything
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
dry_run: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Manage registered workers
|
/// Manage registered workers
|
||||||
Worker {
|
Worker {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|||||||
@@ -7,12 +7,21 @@ use crate::{
|
|||||||
ssh,
|
ssh,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn execute() -> Result<()> {
|
pub fn execute(all: bool) -> Result<()> {
|
||||||
let cfg = config::load()?;
|
let cfg = config::load()?;
|
||||||
let mut jobs = db::list()?;
|
let mut jobs = db::list()?;
|
||||||
|
|
||||||
|
// By default only show running jobs; --all includes finished ones.
|
||||||
|
if !all {
|
||||||
|
jobs.retain(|j| j.status == JobStatus::Running || j.status == JobStatus::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
if jobs.is_empty() {
|
if jobs.is_empty() {
|
||||||
println!("No jobs yet. Run 'p -- <command>' to start one.");
|
if all {
|
||||||
|
println!("No jobs yet. Run 'p -- <command>' to start one.");
|
||||||
|
} else {
|
||||||
|
println!("No running jobs. Use 'p ls --all' to see finished jobs.");
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod attach;
|
pub mod attach;
|
||||||
pub mod logs;
|
pub mod logs;
|
||||||
pub mod ls;
|
pub mod ls;
|
||||||
|
pub mod prune;
|
||||||
pub mod pull;
|
pub mod pull;
|
||||||
pub mod rm;
|
pub mod rm;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
|||||||
79
p/src/commands/prune.rs
Normal file
79
p/src/commands/prune.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::{config, db, job::JobStatus, ssh};
|
||||||
|
|
||||||
|
pub fn execute(force: bool, dry_run: bool) -> Result<()> {
|
||||||
|
let cfg = config::load()?;
|
||||||
|
let jobs = db::list()?;
|
||||||
|
|
||||||
|
let to_prune: Vec<_> = jobs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|j| match j.status {
|
||||||
|
JobStatus::Done | JobStatus::Failed | JobStatus::Stopped => true,
|
||||||
|
JobStatus::Unknown => force,
|
||||||
|
JobStatus::Running => false,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if to_prune.is_empty() {
|
||||||
|
println!("Nothing to prune.");
|
||||||
|
if !force {
|
||||||
|
println!("(use --force to also include jobs with unknown status)");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if dry_run {
|
||||||
|
println!("Would remove {} job(s):", to_prune.len());
|
||||||
|
} else {
|
||||||
|
println!("Removing {} job(s)...", to_prune.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut removed = 0;
|
||||||
|
let mut errors = 0;
|
||||||
|
|
||||||
|
for job in &to_prune {
|
||||||
|
let remote_rm = format!(
|
||||||
|
"rm -rf {}/.p/jobs/{id} {}/.p/workdirs/{id}",
|
||||||
|
// use the worker's home via SSH expansion
|
||||||
|
"~",
|
||||||
|
"~",
|
||||||
|
id = job.id
|
||||||
|
);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
" {} {} ({})",
|
||||||
|
job.short_id(),
|
||||||
|
job.status_display(),
|
||||||
|
job.command_display(40)
|
||||||
|
);
|
||||||
|
|
||||||
|
if dry_run {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort remote cleanup.
|
||||||
|
if let Some(worker) = cfg.get_worker(&job.worker) {
|
||||||
|
if let Err(e) = ssh::run_capture(worker, &remote_rm) {
|
||||||
|
eprintln!(" warning: could not remove remote files: {}", e);
|
||||||
|
errors += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db::delete(&job.id)?;
|
||||||
|
removed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dry_run {
|
||||||
|
if errors > 0 {
|
||||||
|
eprintln!(
|
||||||
|
"Removed {} local record(s); {} remote cleanup(s) failed.",
|
||||||
|
removed, errors
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("Done. Removed {} job(s).", removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ fn main() -> Result<()> {
|
|||||||
// All other subcommands go through clap.
|
// All other subcommands go through clap.
|
||||||
let cli = cli::Cli::parse();
|
let cli = cli::Cli::parse();
|
||||||
match cli.command {
|
match cli.command {
|
||||||
cli::Command::Ls => commands::ls::execute(),
|
cli::Command::Ls { all } => commands::ls::execute(all),
|
||||||
cli::Command::Attach { job_id } => commands::attach::execute(&job_id),
|
cli::Command::Attach { job_id } => commands::attach::execute(&job_id),
|
||||||
cli::Command::Logs { job_id, follow } => commands::logs::execute(&job_id, follow),
|
cli::Command::Logs { job_id, follow } => commands::logs::execute(&job_id, follow),
|
||||||
cli::Command::Stop { job_id } => commands::stop::execute(&job_id),
|
cli::Command::Stop { job_id } => commands::stop::execute(&job_id),
|
||||||
@@ -63,6 +63,7 @@ fn main() -> Result<()> {
|
|||||||
local_dest,
|
local_dest,
|
||||||
} => commands::pull::execute(&job_id, &remote_path, local_dest.as_deref()),
|
} => commands::pull::execute(&job_id, &remote_path, local_dest.as_deref()),
|
||||||
cli::Command::Rm { job_id, force } => commands::rm::execute(&job_id, force),
|
cli::Command::Rm { job_id, force } => commands::rm::execute(&job_id, force),
|
||||||
|
cli::Command::Prune { force, dry_run } => commands::prune::execute(force, dry_run),
|
||||||
cli::Command::Worker { command } => match command {
|
cli::Command::Worker { command } => match command {
|
||||||
cli::WorkerCommand::Ls { check } => commands::worker::ls::execute(check),
|
cli::WorkerCommand::Ls { check } => commands::worker::ls::execute(check),
|
||||||
cli::WorkerCommand::Register { connection, name } => {
|
cli::WorkerCommand::Register { connection, name } => {
|
||||||
|
|||||||
Reference in New Issue
Block a user