From 2bd6f20f73824c86c7ee7cd4d9d6055830f6522b Mon Sep 17 00:00:00 2001 From: James Westby Date: Thu, 1 Aug 2024 21:03:38 +0100 Subject: [PATCH] Add a simple status command --- src/cmd/mod.rs | 9 +++++- src/cmd/status.rs | 52 ++++++++++++++++++++++++++++++++ src/commands.rs | 29 ++++++++++++++++++ src/target.rs | 30 ++++++++++++++++++ src/targets/command/container.rs | 20 ++++++++++-- src/targets/command/exec.rs | 15 +++++++-- tests/test_start.rs | 25 +++++++++++++++ tests/test_status.rs | 21 +++++++++++++ 8 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 src/cmd/status.rs create mode 100644 tests/test_start.rs create mode 100644 tests/test_status.rs diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 6f6ebf5..0293bc5 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -8,6 +8,7 @@ mod execute; mod list; mod run; mod start; +mod status; mod stop; use crate::cleanup::CleanupManager; @@ -17,6 +18,7 @@ pub use execute::Execute; use list::ListCommand; use run::RunCommand; use start::StartCommand; +use status::StatusCommand; use stop::StopCommand; #[derive(Parser, Debug)] @@ -54,7 +56,11 @@ pub enum Commands { /// List available targets List(ListCommand), - // TODO: status, logs + + /// Get the status of a daemon + Status(StatusCommand), + + // TODO: logs } impl Execute for Commands { @@ -65,6 +71,7 @@ impl Execute for Commands { Commands::Stop(cmd) => cmd.execute(context, cleanup_manager), Commands::Build(cmd) => cmd.execute(context, cleanup_manager), Commands::List(cmd) => cmd.execute(context, cleanup_manager), + Commands::Status(cmd) => cmd.execute(context, cleanup_manager), } } } diff --git a/src/cmd/status.rs b/src/cmd/status.rs new file mode 100644 index 0000000..a3ecc6d --- /dev/null +++ b/src/cmd/status.rs @@ -0,0 +1,52 @@ +use std::sync::{Arc, Mutex}; + +use anyhow::{anyhow, Result}; +use clap::Parser; + +use crate::cleanup::CleanupManager; +use crate::cmd::execute::Execute; +use crate::context::{CommandLookupResult, Context}; +use crate::outputs::OutputsManager; +use crate::target::{Targetable, StatusResult}; + +#[derive(Parser, Debug)] +pub struct StatusCommand { + /// The name of the target to get status for + pub name: String, +} + +impl Execute for StatusCommand { + fn execute(&self, context: Context, _cleanup_manager: Arc>) -> Result<()> { + let mut outputs = OutputsManager::default(); + match context.get_target(self.name.as_str()) { + CommandLookupResult::Found(target) => { + let builder = target.as_startable(); + if let Some(builder) = builder { + match builder.status(&context, &mut outputs) { + Ok(StatusResult::Running(msg)) => Ok(println!("[{}] {}", target.target_info().name, msg.as_str())), + Ok(StatusResult::NotRunning()) => Ok(println!("[{}] Not running", target.target_info().name)), + Err(e) => Err(e), + } + } else { + Err(anyhow!( + "Target <{}> is not startable", + self.name + )) + } + }, + CommandLookupResult::NotFound => { + Err(anyhow!( + "Target <{}> not found in config file <{}>", + self.name, + context.config_path + )) + }, + CommandLookupResult::Duplicates(duplicates) => { + Err(anyhow!( + "Target <{}> is ambiguous, possible values are <{}>, please specify the command to run using one of those names", + self.name, duplicates.join(", ") + )) + }, + } + } +} diff --git a/src/commands.rs b/src/commands.rs index c23fbb6..db36503 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -200,6 +200,35 @@ pub fn stop_using_pidfile(pid_path: &std::path::PathBuf, on_stop: impl Fn()) -> Ok(()) } +pub fn status_using_pidfile(pid_path: &std::path::PathBuf) -> Result> { + let mut pid_str = std::fs::read_to_string(pid_path); + match pid_str { + Err(e) => match e.kind() { + std::io::ErrorKind::NotFound => Ok(None), + _ => Err(anyhow!( + "Error reading pid file for target at <{}>: {}", + pid_path.display(), + e + )), + }, + Ok(ref mut pid_str) => { + let pid_str = pid_str.trim().to_string(); + debug!( + "Found pid <{}> for target at <{}>", + pid_str, + pid_path.display() + ); + + let pid = nix::unistd::Pid::from_raw(pid_str.parse::()?); + if is_process_alive(pid) { + Ok(Some(format!("Process running with pid <{}>", pid))) + } else { + Ok(None) + } + }, + } +} + /* use daemonize::{Daemonize, Outcome}; let daemonize = Daemonize::new() diff --git a/src/target.rs b/src/target.rs index 4652c48..64c08c7 100644 --- a/src/target.rs +++ b/src/target.rs @@ -532,6 +532,16 @@ impl Startable for Command { // TODO: last run file? Ok(()) } + + + fn status( + &self, + context: &Context, + outputs: &mut OutputsManager, + ) -> Result { + self.inner_as_startable() + .status(context, outputs) + } } impl Command { @@ -567,6 +577,20 @@ pub trait Runnable { ) -> Result<()>; } +pub enum StatusResult { + Running(String), + NotRunning(), +} + +impl Into for Option { + fn into(self) -> StatusResult { + match self { + None => StatusResult::NotRunning(), + Some(s) => StatusResult::Running(s), + } + } +} + pub trait Startable { fn start( &self, @@ -582,6 +606,12 @@ pub trait Startable { outputs: &mut OutputsManager, cleanup_manager: Arc>, ) -> Result<()>; + + fn status( + &self, + context: &Context, + outputs: &mut OutputsManager, + ) -> Result; } pub trait Buildable { diff --git a/src/targets/command/container.rs b/src/targets/command/container.rs index 12b259f..c460485 100644 --- a/src/targets/command/container.rs +++ b/src/targets/command/container.rs @@ -6,14 +6,14 @@ use log::{debug, info}; use validator::Validate; use crate::cleanup::CleanupManager; -use crate::commands::{run_command, spawn_command_with_pidfile, stop_using_pidfile}; +use crate::commands::{run_command, spawn_command_with_pidfile, stop_using_pidfile, status_using_pidfile}; use crate::config::ContainerCommand as ConfigContainerCommand; use crate::context::Context; use crate::default::{default_optional, default_to}; use crate::outputs::OutputsManager; use crate::rand::rand_string; use crate::shell::{escape_and_prepend, escape_and_prepend_vec, escape_string}; -use crate::target::{create_metadata_dir, CommandInfo, Runnable, Startable, TargetInfo}; +use crate::target::{create_metadata_dir, CommandInfo, Runnable, Startable, TargetInfo, StatusResult}; #[derive(Debug, Clone, Validate)] pub struct ContainerCommand { @@ -189,6 +189,22 @@ impl Startable for ContainerCommand { }; stop_using_pidfile(&pid_path, log_stop) } + + fn status( + &self, + _context: &Context, + _outputs: &mut OutputsManager, + ) -> Result { + let config_dir = create_metadata_dir(self.target_info.name.to_string().as_str())?; + + let pid_path = config_dir.join("pid"); + debug!( + "Searching for pid file for target <{}> at <{}>", + self.target_info.name, + pid_path.display() + ); + status_using_pidfile(&pid_path).map(|s| s.into()) + } } pub struct ContainerRunInfo { diff --git a/src/targets/command/exec.rs b/src/targets/command/exec.rs index 8ccdd2d..5fb8812 100644 --- a/src/targets/command/exec.rs +++ b/src/targets/command/exec.rs @@ -5,13 +5,13 @@ use log::{debug, info}; use validator::Validate; use crate::cleanup::CleanupManager; -use crate::commands::{run_command_with_env, spawn_command_with_pidfile, stop_using_pidfile}; +use crate::commands::{run_command_with_env, spawn_command_with_pidfile, stop_using_pidfile, status_using_pidfile}; use crate::config::ExecCommand as ConfigExecCommand; use crate::context::Context; use crate::default::{default_optional, default_to}; use crate::outputs::OutputsManager; use crate::target::create_metadata_dir; -use crate::target::{CommandInfo, Runnable, Startable, TargetInfo}; +use crate::target::{CommandInfo, Runnable, Startable, TargetInfo, StatusResult}; #[derive(Debug, Clone, Validate)] pub struct ExecCommand { @@ -147,4 +147,15 @@ impl Startable for ExecCommand { }; stop_using_pidfile(&pid_path, log_stop) } + + fn status( + &self, + _context: &Context, + _outputs: &mut OutputsManager, + ) -> Result { + let config_dir = create_metadata_dir(self.target_info.name.to_string().as_str())?; + + let pid_path = config_dir.join("pid"); + status_using_pidfile(&pid_path).map(|s| s.into()) + } } diff --git a/tests/test_start.rs b/tests/test_start.rs new file mode 100644 index 0000000..cae0c53 --- /dev/null +++ b/tests/test_start.rs @@ -0,0 +1,25 @@ +use assert_cmd::prelude::*; + +mod common; + +#[test] +fn test_start() { + let config_src = r#" + [command.exec.do_stuff] + command = "bash -c '$i=0; while $i<10; do i+=1; date; sleep 1; done'" + daemon = true + "#; + + let test_context = common::TestContext::new(); + test_context.write_config(config_src); + + let mut cmd = test_context.get_command(); + cmd.arg("start").arg("do_stuff"); + + cmd.assert().success(); + + let mut cmd = test_context.get_command(); + cmd.arg("stop").arg("do_stuff"); + + cmd.assert().success(); +} diff --git a/tests/test_status.rs b/tests/test_status.rs new file mode 100644 index 0000000..5b886be --- /dev/null +++ b/tests/test_status.rs @@ -0,0 +1,21 @@ +use assert_cmd::prelude::*; +use predicates::prelude::*; + +mod common; + +#[test] +fn test_status_not_started() { + let config_src = r#" + [command.exec.do_stuff] + command = "bash -c '$i=0; while $i<10; do i+=1; date; sleep 1; done'" + daemon = true + "#; + + let test_context = common::TestContext::new(); + test_context.write_config(config_src); + + let mut cmd = test_context.get_command(); + cmd.arg("status").arg("do_stuff"); + + cmd.assert().success().stdout(predicate::str::contains("[command.exec.do_stuff] Not running")); +}