From 490ad28584a5287924b24d1cd3eb842a8551dd66 Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Tue, 22 Aug 2023 00:13:31 +0200 Subject: [PATCH 1/5] fix: Implement proper Default for settings --- pueue/tests/helper/fixtures.rs | 18 ++------- pueue_lib/src/settings.rs | 72 ++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/pueue/tests/helper/fixtures.rs b/pueue/tests/helper/fixtures.rs index 60dc3196..8d2eba38 100644 --- a/pueue/tests/helper/fixtures.rs +++ b/pueue/tests/helper/fixtures.rs @@ -139,36 +139,24 @@ pub fn daemon_base_setup() -> Result<(Settings, TempDir)> { pueue_directory: Some(tempdir_path.to_path_buf()), runtime_directory: Some(tempdir_path.to_path_buf()), alias_file: Some(tempdir_path.join("pueue_aliases.yml")), - #[cfg(not(target_os = "windows"))] - use_unix_socket: true, - #[cfg(not(target_os = "windows"))] - unix_socket_path: None, - pid_path: None, host: "localhost".to_string(), port: "51230".to_string(), daemon_cert: Some(tempdir_path.join("certs").join("daemon.cert")), daemon_key: Some(tempdir_path.join("certs").join("daemon.key")), shared_secret_path: Some(tempdir_path.join("secret")), + ..Default::default() }; let client = Client { - restart_in_place: false, - read_local_logs: true, - show_confirmation_questions: false, - show_expanded_aliases: false, - dark_mode: false, max_status_lines: Some(15), - status_time_format: "%H:%M:%S".into(), status_datetime_format: "%Y-%m-%d %H:%M:%S".into(), + ..Default::default() }; #[allow(deprecated)] let daemon = Daemon { - pause_group_on_failure: false, - pause_all_on_failure: false, - callback: None, callback_log_lines: 15, - groups: None, + ..Default::default() }; let settings = Settings { diff --git a/pueue_lib/src/settings.rs b/pueue_lib/src/settings.rs index 38227860..97fa0101 100644 --- a/pueue_lib/src/settings.rs +++ b/pueue_lib/src/settings.rs @@ -11,7 +11,7 @@ use crate::error::Error; use crate::setting_defaults::*; /// All settings which are used by both, the client and the daemon -#[derive(PartialEq, Eq, Clone, Debug, Default, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] pub struct Shared { /// Don't access this property directly, but rather use the getter with the same name. /// It's only public to allow proper integration testing. @@ -74,7 +74,7 @@ pub struct Shared { } /// All settings which are used by the client -#[derive(PartialEq, Eq, Clone, Debug, Default, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] pub struct Client { /// If set to true, all tasks will be restart in place, instead of creating a new task. /// False is the default, as you'll lose the logs of the previously failed tasks when @@ -106,7 +106,7 @@ pub struct Client { } /// All settings which are used by the daemon -#[derive(PartialEq, Eq, Clone, Debug, Default, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] pub struct Daemon { /// Whether a group should be paused as soon as a single task fails #[serde(default = "Default::default")] @@ -128,34 +128,58 @@ pub struct Daemon { pub groups: Option>, } -impl Default for Settings { +impl Default for Shared { + fn default() -> Self { + Shared { + pueue_directory: None, + runtime_directory: None, + alias_file: None, + + #[cfg(not(target_os = "windows"))] + unix_socket_path: None, + #[cfg(not(target_os = "windows"))] + use_unix_socket: true, + host: default_host(), + port: default_port(), + + pid_path: None, + daemon_cert: None, + daemon_key: None, + shared_secret_path: None, + } + } +} + +impl Default for Client { fn default() -> Self { - Settings { - client: Client { - read_local_logs: true, - status_time_format: default_status_time_format(), - status_datetime_format: default_status_datetime_format(), - ..Default::default() - }, - daemon: Daemon { - callback_log_lines: default_callback_log_lines(), - ..Default::default() - }, - shared: Shared { - #[cfg(not(target_os = "windows"))] - use_unix_socket: true, - host: default_host(), - port: default_port(), - ..Default::default() - }, - profiles: HashMap::new(), + Client { + restart_in_place: false, + read_local_logs: true, + show_confirmation_questions: false, + show_expanded_aliases: false, + dark_mode: false, + max_status_lines: None, + status_time_format: default_status_time_format(), + status_datetime_format: default_status_datetime_format(), + } + } +} + +impl Default for Daemon { + fn default() -> Self { + Daemon { + pause_group_on_failure: false, + pause_all_on_failure: false, + callback: None, + callback_log_lines: default_callback_log_lines(), + groups: None, } } } /// The parent settings struct. \ /// This contains all other setting structs. -#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Clone, Default, Debug, Deserialize, Serialize)] pub struct Settings { #[serde(default = "Default::default")] pub client: Client, From 887c40910445eab7bba9235c27b70f8d898a8461 Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Tue, 22 Aug 2023 00:16:35 +0200 Subject: [PATCH 2/5] remove: Deprecated groups settings field --- pueue/src/bin/pueue.rs | 10 ---------- pueue/src/daemon/mod.rs | 12 +----------- pueue_lib/src/settings.rs | 8 -------- pueue_lib/tests/settings_backward_compatibility.rs | 6 +----- 4 files changed, 2 insertions(+), 34 deletions(-) diff --git a/pueue/src/bin/pueue.rs b/pueue/src/bin/pueue.rs index e05cb036..ee6262a7 100644 --- a/pueue/src/bin/pueue.rs +++ b/pueue/src/bin/pueue.rs @@ -62,16 +62,6 @@ async fn main() -> Result<()> { settings.load_profile(profile)?; } - #[allow(deprecated)] - if settings.daemon.groups.is_some() { - println!( - "Please delete the 'daemon.groups' section from your config file. - Run `pueue -vv` to see where your config is located.\n\ - It is no longer used and groups can now only be edited via the commandline interface.\n\n\ - Attention: The first time the daemon is restarted this update, the amount of parallel tasks per group will be reset to 1!!" - ) - } - // Error if no configuration file can be found, as this is an indicator, that the daemon hasn't // been started yet. if !config_found { diff --git a/pueue/src/daemon/mod.rs b/pueue/src/daemon/mod.rs index c3c7f2e4..c8789bee 100644 --- a/pueue/src/daemon/mod.rs +++ b/pueue/src/daemon/mod.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use std::{fs::create_dir_all, path::PathBuf}; use anyhow::{bail, Context, Result}; -use log::{error, warn}; +use log::warn; use std::sync::mpsc::channel; use pueue_lib::error::Error; @@ -52,16 +52,6 @@ pub async fn run(config_path: Option, profile: Option, test: bo settings.load_profile(profile)?; } - #[allow(deprecated)] - if settings.daemon.groups.is_some() { - error!( - "Please delete the 'daemon.groups' section from your config file. - Run `pueue -vv` to see where your config is located.\n\ - It is no longer used and groups can now only be edited via the commandline interface. \n\n\ - Attention: The first time the daemon is restarted this update, the amount of parallel tasks per group will be reset to 1!!" - ) - } - init_directories(&settings.shared.pueue_directory())?; if !settings.shared.daemon_key().exists() && !settings.shared.daemon_cert().exists() { create_certificates(&settings.shared).context("Failed to create certificates.")?; diff --git a/pueue_lib/src/settings.rs b/pueue_lib/src/settings.rs index 97fa0101..9058bc4c 100644 --- a/pueue_lib/src/settings.rs +++ b/pueue_lib/src/settings.rs @@ -119,13 +119,6 @@ pub struct Daemon { /// The amount of log lines from stdout/stderr that are passed to the callback command. #[serde(default = "default_callback_log_lines")] pub callback_log_lines: usize, - /// The legacy configuration for groups - #[serde(skip_serializing)] - #[deprecated( - since = "1.1.0", - note = "The configuration for groups is now stored in the state." - )] - pub groups: Option>, } impl Default for Shared { @@ -172,7 +165,6 @@ impl Default for Daemon { pause_all_on_failure: false, callback: None, callback_log_lines: default_callback_log_lines(), - groups: None, } } } diff --git a/pueue_lib/tests/settings_backward_compatibility.rs b/pueue_lib/tests/settings_backward_compatibility.rs index ecabe36a..93e9bd3e 100644 --- a/pueue_lib/tests/settings_backward_compatibility.rs +++ b/pueue_lib/tests/settings_backward_compatibility.rs @@ -23,14 +23,10 @@ fn test_restore_from_old_state() -> Result<()> { .join("v0.15.0_settings.yml"); // Open v0.15.0 file and ensure the settings file can be read. - let (settings, config_found) = Settings::read(&Some(old_settings_path)) + let (_settings, config_found) = Settings::read(&Some(old_settings_path)) .context("Failed to read old config with defaults:")?; assert!(config_found); - // Legacy group setting exists - #[allow(deprecated)] - let groups = settings.daemon.groups.unwrap(); - assert_eq!(*groups.get("webhook").unwrap(), 1); Ok(()) } From 22b7fc0c1d9ae173c45ac1324373d67c2902e601 Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Fri, 18 Aug 2023 20:09:21 +0200 Subject: [PATCH 3/5] add: make shell command configurable --- CHANGELOG.md | 4 ++ Cargo.lock | 1 + Cargo.toml | 1 + pueue/Cargo.toml | 2 +- pueue/src/client/client.rs | 11 ++++- pueue/src/client/commands/edit.rs | 14 +++--- pueue/src/client/commands/restart.rs | 3 ++ pueue/src/daemon/task_handler/callback.rs | 2 +- pueue/src/daemon/task_handler/spawn_task.rs | 2 +- pueue_lib/Cargo.toml | 1 + pueue_lib/src/process_helper/mod.rs | 40 ++++++++++++++++- pueue_lib/src/process_helper/unix.rs | 49 +++++++++++++-------- pueue_lib/src/process_helper/windows.rs | 45 ++++++++++++------- pueue_lib/src/settings.rs | 10 +++++ 14 files changed, 140 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f29a8c..5935562d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - QoL improvement: Don't pause groups if there're no queued tasks. [#452](https://github.com/Nukesor/pueue/issues/452) Auto-pausing of groups was only done to prevent the unwanted execution of other tasks, but this isn't necessary, if there're no queued tasks. +### Added + +- Allow configuration of the shell command that executes task commands. [#454](https://github.com/Nukesor/pueue/issues/454) + ## [3.2.0] - 2023-06-13 ### Added diff --git a/Cargo.lock b/Cargo.lock index 30d208ce..598f7daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,6 +1213,7 @@ dependencies = [ "chrono", "command-group", "dirs", + "handlebars", "libproc", "log", "portpicker", diff --git a/Cargo.toml b/Cargo.toml index 517c160d..b7de071d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ snap = "1.1" strum = "0.25" strum_macros = "0.25" tokio = { version = "1.32", features = ["rt-multi-thread", "time", "io-std"] } +handlebars = "4.3" # Dev dependencies anyhow = "1" diff --git a/pueue/Cargo.toml b/pueue/Cargo.toml index 9593dc39..56bcb376 100644 --- a/pueue/Cargo.toml +++ b/pueue/Cargo.toml @@ -24,7 +24,6 @@ clap_complete = "4.3" comfy-table = "7" crossterm = { version = "0.26", default-features = false } ctrlc = { version = "3", features = ["termination"] } -handlebars = "4.3" pest = "2.7" pest_derive = "2.7" shell-escape = "0.1" @@ -33,6 +32,7 @@ tempfile = "3" chrono = { workspace = true } command-group = { workspace = true } +handlebars = { workspace = true } log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/pueue/src/client/client.rs b/pueue/src/client/client.rs index 2dbe388e..1da8e8e5 100644 --- a/pueue/src/client/client.rs +++ b/pueue/src/client/client.rs @@ -199,7 +199,15 @@ impl Client { path, label, } => { - let message = edit(&mut self.stream, *task_id, *command, *path, *label).await?; + let message = edit( + &mut self.stream, + &self.settings, + *task_id, + *command, + *path, + *label, + ) + .await?; self.handle_response(message)?; Ok(true) } @@ -231,6 +239,7 @@ impl Client { (self.settings.client.restart_in_place || *in_place) && !*not_in_place; restart( &mut self.stream, + &self.settings, task_ids.clone(), *all_failed, failed_in_group.clone(), diff --git a/pueue/src/client/commands/edit.rs b/pueue/src/client/commands/edit.rs index 79b89056..592a9945 100644 --- a/pueue/src/client/commands/edit.rs +++ b/pueue/src/client/commands/edit.rs @@ -3,6 +3,7 @@ use std::io::{Read, Seek, Write}; use std::path::{Path, PathBuf}; use anyhow::{bail, Context, Result}; +use pueue_lib::settings::Settings; use tempfile::NamedTempFile; use pueue_lib::network::message::*; @@ -18,6 +19,7 @@ use pueue_lib::process_helper::compile_shell_command; /// Upon exiting the text editor, the line will then be read and sent to the server pub async fn edit( stream: &mut GenericStream, + settings: &Settings, task_id: usize, edit_command: bool, edit_path: bool, @@ -41,6 +43,7 @@ pub async fn edit( // Edit all requested properties. let edit_result = edit_task_properties( + settings, &init_response.command, &init_response.path, &init_response.label, @@ -100,6 +103,7 @@ pub struct EditedProperties { /// /// The returned values are: `(command, path, label)` pub fn edit_task_properties( + settings: &Settings, original_command: &str, original_path: &Path, original_label: &Option, @@ -111,7 +115,7 @@ pub fn edit_task_properties( // Update the command if requested. if edit_command { - props.command = Some(edit_line(original_command)?); + props.command = Some(edit_line(settings, original_command)?); }; // Update the path if requested. @@ -119,13 +123,13 @@ pub fn edit_task_properties( let str_path = original_path .to_str() .context("Failed to convert task path to string")?; - let changed_path = edit_line(str_path)?; + let changed_path = edit_line(settings, str_path)?; props.path = Some(PathBuf::from(changed_path)); } // Update the label if requested. if edit_label { - let edited_label = edit_line(&original_label.clone().unwrap_or_default())?; + let edited_label = edit_line(settings, &original_label.clone().unwrap_or_default())?; // If the user deletes the label in their editor, an empty string will be returned. // This is an indicator that the task should no longer have a label, in which case we @@ -143,7 +147,7 @@ pub fn edit_task_properties( /// This function enables the user to edit a task's details. /// Save any string to a temporary file, which is opened in the specified `$EDITOR`. /// As soon as the editor is closed, read the file content and return the line. -fn edit_line(line: &str) -> Result { +fn edit_line(settings: &Settings, line: &str) -> Result { // Create a temporary file with the command so we can edit it with the editor. let mut file = NamedTempFile::new().expect("Failed to create a temporary file"); writeln!(file, "{line}").context("Failed to write to temporary file.")?; @@ -158,7 +162,7 @@ fn edit_line(line: &str) -> Result { // We escape the file path for good measure, but it shouldn't be necessary. let path = shell_escape::escape(file.path().to_string_lossy()); let editor_command = format!("{editor} {path}"); - let status = compile_shell_command(&editor_command) + let status = compile_shell_command(settings, &editor_command) .status() .context("Editor command did somehow fail. Aborting.")?; diff --git a/pueue/src/client/commands/restart.rs b/pueue/src/client/commands/restart.rs index efe08523..6249ee41 100644 --- a/pueue/src/client/commands/restart.rs +++ b/pueue/src/client/commands/restart.rs @@ -2,6 +2,7 @@ use anyhow::{bail, Result}; use pueue_lib::network::message::*; use pueue_lib::network::protocol::*; +use pueue_lib::settings::Settings; use pueue_lib::state::FilteredTasks; use pueue_lib::task::{Task, TaskResult, TaskStatus}; @@ -16,6 +17,7 @@ use crate::client::commands::get_state; #[allow(clippy::too_many_arguments)] pub async fn restart( stream: &mut GenericStream, + settings: &Settings, task_ids: Vec, all_failed: bool, failed_in_group: Option, @@ -85,6 +87,7 @@ pub async fn restart( // Edit any properties, if requested. let edited_props = edit_task_properties( + settings, &task.command, &task.path, &task.label, diff --git a/pueue/src/daemon/task_handler/callback.rs b/pueue/src/daemon/task_handler/callback.rs index c13cfefb..31f5f71b 100644 --- a/pueue/src/daemon/task_handler/callback.rs +++ b/pueue/src/daemon/task_handler/callback.rs @@ -20,7 +20,7 @@ impl TaskHandler { } }; - let mut command = compile_shell_command(&callback_command); + let mut command = compile_shell_command(&self.settings, &callback_command); // Spawn the callback subprocess and log if it fails. let spawn_result = command.spawn(); diff --git a/pueue/src/daemon/task_handler/spawn_task.rs b/pueue/src/daemon/task_handler/spawn_task.rs index d1ed07d3..ad73ff4c 100644 --- a/pueue/src/daemon/task_handler/spawn_task.rs +++ b/pueue/src/daemon/task_handler/spawn_task.rs @@ -135,7 +135,7 @@ impl TaskHandler { }; // Build the shell command that should be executed. - let mut command = compile_shell_command(&command); + let mut command = compile_shell_command(&self.settings, &command); // Determine the worker's id depending on the current group. // Inject that info into the environment. diff --git a/pueue_lib/Cargo.toml b/pueue_lib/Cargo.toml index 80232635..eab800ce 100644 --- a/pueue_lib/Cargo.toml +++ b/pueue_lib/Cargo.toml @@ -32,6 +32,7 @@ thiserror = "1.0" tokio-rustls = { version = "0.24", default-features = false } command-group = { workspace = true } +handlebars = { workspace = true } log = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/pueue_lib/src/process_helper/mod.rs b/pueue_lib/src/process_helper/mod.rs index f317942f..c51a8b9f 100644 --- a/pueue_lib/src/process_helper/mod.rs +++ b/pueue_lib/src/process_helper/mod.rs @@ -4,7 +4,9 @@ //! each supported platform. //! Depending on the target, the respective platform is read and loaded into this scope. -use crate::network::message::Signal as InternalSignal; +use std::{collections::HashMap, process::Command}; + +use crate::{network::message::Signal as InternalSignal, settings::Settings}; // Unix specific process handling // Shared between Linux and Apple @@ -63,3 +65,39 @@ impl From for Signal { } } } + +/// Take a platform specific shell command and insert the actual task command via templating. +pub fn compile_shell_command(settings: &Settings, command: &str) -> Command { + let shell_command = get_shell_command(settings); + + let mut handlebars = handlebars::Handlebars::new(); + handlebars.set_strict_mode(true); + handlebars.register_escape_fn(handlebars::no_escape); + + // Make the command available to the template engine. + let mut parameters = HashMap::new(); + parameters.insert("pueue_command_string", command); + + // We allow users to provide their own shell command. + // They should use the `{{ pueue_command_string }}` placeholder. + let mut compiled_command = Vec::new(); + for part in shell_command { + let compiled_part = handlebars + .render_template(&part, ¶meters) + .unwrap_or_else(|_| { + panic!("Failed to render shell command for template: {part} and parameters: {parameters:?}") + }); + + compiled_command.push(compiled_part); + } + + let executable = compiled_command.remove(0); + + // Chain two `powershell` commands, one that sets the output encoding to utf8 and then the user provided one. + let mut command = Command::new(executable); + for arg in compiled_command { + command.arg(&arg); + } + + command +} diff --git a/pueue_lib/src/process_helper/unix.rs b/pueue_lib/src/process_helper/unix.rs index 10a720e0..15014696 100644 --- a/pueue_lib/src/process_helper/unix.rs +++ b/pueue_lib/src/process_helper/unix.rs @@ -1,5 +1,3 @@ -use std::process::Command; - // We allow anyhow in here, as this is a module that'll be strictly used internally. // As soon as it's obvious that this is code is intended to be exposed to library users, we have to // go ahead and replace any `anyhow` usage by proper error handling via our own Error type. @@ -7,11 +5,18 @@ use anyhow::Result; use command_group::{GroupChild, Signal, UnixChildExt}; use log::info; -pub fn compile_shell_command(command_string: &str) -> Command { - let mut command = Command::new("sh"); - command.arg("-c").arg(command_string); +use crate::settings::Settings; + +pub fn get_shell_command(settings: &Settings) -> Vec { + let Some(ref shell_command) = settings.daemon.shell_command else { + return vec![ + "sh".into(), + "-c".into(), + "{{ pueue_command_string }}".into(), + ]; + }; - command + shell_command.clone() } /// Send a signal to one of Pueue's child process group handle. @@ -39,17 +44,18 @@ pub fn kill_child(task_id: usize, child: &mut GroupChild) -> std::io::Result<()> #[cfg(test)] mod tests { - use log::warn; + use std::process::Command; use std::thread::sleep; use std::time::Duration; use anyhow::Result; use command_group::CommandGroup; use libproc::processes::{pids_by_type, ProcFilter}; + use log::warn; use pretty_assertions::assert_eq; use super::*; - use crate::process_helper::process_exists; + use crate::process_helper::{compile_shell_command, process_exists}; /// List all PIDs that are part of the process group pub fn get_process_group_pids(pgrp: u32) -> Vec { @@ -75,7 +81,8 @@ mod tests { #[test] fn test_spawn_command() { - let mut child = compile_shell_command("sleep 0.1") + let settings = Settings::default(); + let mut child = compile_shell_command(&settings, "sleep 0.1") .group_spawn() .expect("Failed to spawn echo"); @@ -87,9 +94,11 @@ mod tests { #[test] /// Ensure a `sh -c` command will be properly killed without detached processes. fn test_shell_command_is_killed() -> Result<()> { - let mut child = compile_shell_command("sleep 60 & sleep 60 && echo 'this is a test'") - .group_spawn() - .expect("Failed to spawn echo"); + let settings = Settings::default(); + let mut child = + compile_shell_command(&settings, "sleep 60 & sleep 60 && echo 'this is a test'") + .group_spawn() + .expect("Failed to spawn echo"); let pid = child.id(); // Sleep a little to give everything a chance to spawn. sleep(Duration::from_millis(500)); @@ -120,9 +129,11 @@ mod tests { /// Ensure a `sh -c` command will be properly killed without detached processes when using unix /// signals directly. fn test_shell_command_is_killed_with_signal() -> Result<()> { - let mut child = compile_shell_command("sleep 60 & sleep 60 && echo 'this is a test'") - .group_spawn() - .expect("Failed to spawn echo"); + let settings = Settings::default(); + let mut child = + compile_shell_command(&settings, "sleep 60 & sleep 60 && echo 'this is a test'") + .group_spawn() + .expect("Failed to spawn echo"); let pid = child.id(); // Sleep a little to give everything a chance to spawn. sleep(Duration::from_millis(500)); @@ -153,9 +164,11 @@ mod tests { /// Ensure that a `sh -c` process with a child process that has children of its own /// will properly kill all processes and their children's children without detached processes. fn test_shell_command_children_are_killed() -> Result<()> { - let mut child = compile_shell_command("bash -c 'sleep 60 && sleep 60' && sleep 60") - .group_spawn() - .expect("Failed to spawn echo"); + let settings = Settings::default(); + let mut child = + compile_shell_command(&settings, "bash -c 'sleep 60 && sleep 60' && sleep 60") + .group_spawn() + .expect("Failed to spawn echo"); let pid = child.id(); // Sleep a little to give everything a chance to spawn. sleep(Duration::from_millis(500)); diff --git a/pueue_lib/src/process_helper/windows.rs b/pueue_lib/src/process_helper/windows.rs index 9753a403..57f2ba7a 100644 --- a/pueue_lib/src/process_helper/windows.rs +++ b/pueue_lib/src/process_helper/windows.rs @@ -1,5 +1,3 @@ -use std::process::Command; - // We allow anyhow in here, as this is a module that'll be strictly used internally. // As soon as it's obvious that this is code is intended to be exposed to library users, we have to // go ahead and replace any `anyhow` usage by proper error handling via our own Error type. @@ -17,6 +15,8 @@ use winapi::um::tlhelp32::{ }; use winapi::um::winnt::THREAD_SUSPEND_RESUME; +use crate::settings::Settings; + /// Shim signal enum for windows. pub enum Signal { SIGINT, @@ -26,14 +26,18 @@ pub enum Signal { SIGSTOP, } -pub fn compile_shell_command(command_string: &str) -> Command { - // Chain two `powershell` commands, one that sets the output encoding to utf8 and then the user provided one. - let mut command = Command::new("powershell"); - command.arg("-c").arg(format!( - "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8; {command_string}" - )); - - command +pub fn get_shell_command(settings: &Settings) -> Vec { + let Some(ref shell_command) = settings.daemon.shell_command else { + // Chain two `powershell` commands, one that sets the output encoding to utf8 and then the user provided one. + return vec![ + "powershell".into(), + "-c".into(), + "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8; {{ pueue_command_string }}" + .into(), + ]; + }; + + shell_command.clone() } /// Send a signal to a windows process. @@ -251,12 +255,14 @@ pub fn process_exists(pid: u32) -> bool { #[cfg(test)] mod test { + use std::process::Command; use std::thread::sleep; use std::time::Duration; use command_group::CommandGroup; use super::*; + use crate::process_helper::compile_shell_command; /// Assert that certain process id no longer exists fn process_is_gone(pid: u32) -> bool { @@ -292,7 +298,8 @@ mod test { #[test] fn test_spawn_command() { - let mut child = compile_shell_command("sleep 0.1") + let settings = Settings::default(); + let mut child = compile_shell_command(&settings, "sleep 0.1") .group_spawn() .expect("Failed to spawn echo"); @@ -308,9 +315,11 @@ mod test { /// This test is ignored for now, as it is flaky from time to time. /// See https://github.com/Nukesor/pueue/issues/315 fn test_shell_command_is_killed() -> Result<()> { - let mut child = compile_shell_command("sleep 60; sleep 60; echo 'this is a test'") - .group_spawn() - .expect("Failed to spawn echo"); + let settings = Settings::default(); + let mut child = + compile_shell_command(&settings, "sleep 60; sleep 60; echo 'this is a test'") + .group_spawn() + .expect("Failed to spawn echo"); let pid = child.id(); // Get all processes, so we can make sure they no longer exist afterwards. @@ -338,9 +347,11 @@ mod test { /// Ensure that a `powershell -c` process with a child process that has children of it's own /// will properly kill all processes and their children's children without detached processes. fn test_shell_command_children_are_killed() -> Result<()> { - let mut child = compile_shell_command("powershell -c 'sleep 60; sleep 60'; sleep 60") - .group_spawn() - .expect("Failed to spawn echo"); + let settings = Settings::default(); + let mut child = + compile_shell_command(&settings, "powershell -c 'sleep 60; sleep 60'; sleep 60") + .group_spawn() + .expect("Failed to spawn echo"); let pid = child.id(); // Get all processes, so we can make sure they no longer exist afterwards. let process_ids = assert_process_ids(pid, 2, 5000)?; diff --git a/pueue_lib/src/settings.rs b/pueue_lib/src/settings.rs index 9058bc4c..4f069b33 100644 --- a/pueue_lib/src/settings.rs +++ b/pueue_lib/src/settings.rs @@ -119,6 +119,15 @@ pub struct Daemon { /// The amount of log lines from stdout/stderr that are passed to the callback command. #[serde(default = "default_callback_log_lines")] pub callback_log_lines: usize, + /// The command that should be used for task and callback execution. + /// The following are the only officially supported modi for Pueue. + /// + /// Unix default: + /// `vec!["sh", "-c", "{{ pueue_command_string }}"]`. + /// + /// Windows default: + /// `vec!["powershell", "-c", "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8; {{ pueue_command_string }}"]` + pub shell_command: Option>, } impl Default for Shared { @@ -165,6 +174,7 @@ impl Default for Daemon { pause_all_on_failure: false, callback: None, callback_log_lines: default_callback_log_lines(), + shell_command: None, } } } From fac12ce49462e5218f8f384a3347b643cfd655ef Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Mon, 21 Aug 2023 23:08:20 +0200 Subject: [PATCH 4/5] add: daemon env_vars setting --- CHANGELOG.md | 1 + Cargo.lock | 2 +- pueue/Cargo.toml | 2 +- pueue_lib/CHANGELOG.md | 16 +++++++++++++--- pueue_lib/Cargo.toml | 2 +- pueue_lib/src/process_helper/mod.rs | 9 +++++++++ pueue_lib/src/settings.rs | 4 ++++ 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5935562d..df401309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Allow configuration of the shell command that executes task commands. [#454](https://github.com/Nukesor/pueue/issues/454) +- Allow injection of hard coded environment variables via config file. [#454](https://github.com/Nukesor/pueue/issues/454) ## [3.2.0] - 2023-06-13 diff --git a/Cargo.lock b/Cargo.lock index 598f7daa..9ee45507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "pueue-lib" -version = "0.23.0" +version = "0.24.0" dependencies = [ "anyhow", "async-trait", diff --git a/pueue/Cargo.toml b/pueue/Cargo.toml index 56bcb376..56e4bea7 100644 --- a/pueue/Cargo.toml +++ b/pueue/Cargo.toml @@ -15,7 +15,7 @@ rust-version.workspace = true maintenance = { status = "actively-developed" } [dependencies] -pueue-lib = { version = "0.23.0", path = "../pueue_lib" } +pueue-lib = { version = "0.24.0", path = "../pueue_lib" } anyhow = "1.0" chrono-english = "0.1" diff --git a/pueue_lib/CHANGELOG.md b/pueue_lib/CHANGELOG.md index a679fc9d..63f808e5 100644 --- a/pueue_lib/CHANGELOG.md +++ b/pueue_lib/CHANGELOG.md @@ -6,19 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres **somewhat** to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The concept of SemVer is applied to the daemon/client API, but not the library API itself. -## [0.23.0] - unreleased +## [0.24.0] - 2023-06-13 + +### Added + +- New setting `daemon.shell_command` to configure how the command shall be executed. +- New setting `daemon.env_vars` to inject hard coded environment variables into the process. ### Changed - Refactor `State::filter_*` functions to return proper type. -## [0.22.0] - 2023-06-13 +## [0.23.0] - 2023-06-13 -## Added +### Added - Add `priority` field to `Task` - Remove `tempdir` dependency +## [0.22.0] + +This version was skipped due to a error during release :). + + ## [0.21.3] - 2023-02-12 ### Changed diff --git a/pueue_lib/Cargo.toml b/pueue_lib/Cargo.toml index eab800ce..9c252095 100644 --- a/pueue_lib/Cargo.toml +++ b/pueue_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pueue-lib" -version = "0.23.0" +version = "0.24.0" description = "The shared library to work with the Pueue client and daemon." keywords = ["pueue"] readme = "README.md" diff --git a/pueue_lib/src/process_helper/mod.rs b/pueue_lib/src/process_helper/mod.rs index c51a8b9f..e097580a 100644 --- a/pueue_lib/src/process_helper/mod.rs +++ b/pueue_lib/src/process_helper/mod.rs @@ -99,5 +99,14 @@ pub fn compile_shell_command(settings: &Settings, command: &str) -> Command { command.arg(&arg); } + // Inject custom environment variables. + if !settings.daemon.env_vars.is_empty() { + log::info!( + "Inject environment variables: {:?}", + &settings.daemon.env_vars + ); + command.envs(&settings.daemon.env_vars); + } + command } diff --git a/pueue_lib/src/settings.rs b/pueue_lib/src/settings.rs index 4f069b33..d9342ca3 100644 --- a/pueue_lib/src/settings.rs +++ b/pueue_lib/src/settings.rs @@ -116,6 +116,9 @@ pub struct Daemon { pub pause_all_on_failure: bool, /// The callback that's called whenever a task finishes. pub callback: Option, + /// Enironment variables that can be will be injected into all executed processes. + #[serde(default = "Default::default")] + pub env_vars: HashMap, /// The amount of log lines from stdout/stderr that are passed to the callback command. #[serde(default = "default_callback_log_lines")] pub callback_log_lines: usize, @@ -175,6 +178,7 @@ impl Default for Daemon { callback: None, callback_log_lines: default_callback_log_lines(), shell_command: None, + env_vars: HashMap::new(), } } } From d3927561029a334fac4c99c9880c2903f78a8fcc Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Fri, 29 Sep 2023 11:45:50 +0200 Subject: [PATCH 5/5] docs: Update changelog --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df401309..22ccc6f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Allow configuration of the shell command that executes task commands. [#454](https://github.com/Nukesor/pueue/issues/454) -- Allow injection of hard coded environment variables via config file. [#454](https://github.com/Nukesor/pueue/issues/454) +The two following features are very new and marked as "experimental" for the time being. +They might be reworked in a later release, since working with shells is always tricky and this definitely need more testing. + +- Experimental: Allow configuration of the shell command that executes task commands. [#454](https://github.com/Nukesor/pueue/issues/454) +- Experimental: Allow injection of hard coded environment variables via config file. [#454](https://github.com/Nukesor/pueue/issues/454) ## [3.2.0] - 2023-06-13