Skip to content

Commit

Permalink
Merge pull request #458 from Nukesor/configurable-shell
Browse files Browse the repository at this point in the history
add: make shell command configurable
  • Loading branch information
Nukesor authored Sep 29, 2023
2 parents 1552761 + d392756 commit 96b5fef
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 123 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ 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

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

### Added
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions pueue/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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 }
Expand Down
10 changes: 0 additions & 10 deletions pueue/src/bin/pueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion pueue/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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(),
Expand Down
14 changes: 9 additions & 5 deletions pueue/src/client/commands/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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<String>,
Expand All @@ -111,21 +115,21 @@ 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.
if edit_path {
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
Expand All @@ -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<String> {
fn edit_line(settings: &Settings, line: &str) -> Result<String> {
// 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.")?;
Expand All @@ -158,7 +162,7 @@ fn edit_line(line: &str) -> Result<String> {
// 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.")?;

Expand Down
3 changes: 3 additions & 0 deletions pueue/src/client/commands/restart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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<usize>,
all_failed: bool,
failed_in_group: Option<String>,
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 1 addition & 11 deletions pueue/src/daemon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,16 +52,6 @@ pub async fn run(config_path: Option<PathBuf>, profile: Option<String>, 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.")?;
Expand Down
2 changes: 1 addition & 1 deletion pueue/src/daemon/task_handler/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion pueue/src/daemon/task_handler/spawn_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 3 additions & 15 deletions pueue/tests/helper/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 13 additions & 3 deletions pueue_lib/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pueue_lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 }
Expand Down
49 changes: 48 additions & 1 deletion pueue_lib/src/process_helper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,3 +65,48 @@ impl From<InternalSignal> 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, &parameters)
.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);
}

// 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
}
Loading

0 comments on commit 96b5fef

Please sign in to comment.