From 9f72e85b4c84b38d86afb7fb5e3b4c673a80d130 Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Thu, 4 Apr 2024 23:14:57 -0500 Subject: [PATCH 1/4] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 03cff0a..00ded6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ clap = "4" ctrlc = { version = "3", default-features = false } env_logger = { version = "0.11", default-features = false } log = "0.4" -nix = { version = "0.27", optional = true, default-features = false, features = ["signal"] } +nix = { version = "0.28", optional = true, default-features = false, features = ["signal"] } owo-colors = { version = "4.0", default-features = false } postcard = { version = "1", features = ["use-std"] } serde = { version = "1.0", features = ["derive"] } From eae13f4fd4cc479bfa25681784da149057c8fc9c Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 6 Apr 2024 16:30:50 -0500 Subject: [PATCH 2/4] fixes for Windows --- CHANGELOG.md | 12 ++++++- Cargo.toml | 1 + examples/auto_exec.rs | 28 +++++++++++++++ examples/auto_exec_i.rs | 21 ----------- examples/docker_entrypoint_pattern.rs | 11 +++--- src/command.rs | 2 +- src/docker.rs | 14 ++++++++ src/docker_helpers.rs | 51 ++++++++++++++++++++------- src/paths.rs | 24 ++++++++++--- 9 files changed, 120 insertions(+), 44 deletions(-) create mode 100644 examples/auto_exec.rs delete mode 100644 examples/auto_exec_i.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7a0f4..70ece7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog -## [0.10.0] - 2024-1-20 +## [0.11.0] - 2025-04-06 +### Fixes +- Fixed path canonicalization on Windows to use `dunce::simplify` to avoid UNC paths + +### Changes +- Replaced `auto_exec_i` with `auto_exec` which allows customizing arguments + +### Additions +- Added a `workdir` option to `Container` + +## [0.10.0] - 2024-01-20 ### Fixes - Fixed compilation on Windows - Fixed an issue with an example diff --git a/Cargo.toml b/Cargo.toml index 00ded6c..baff247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ description = "programmable container orchestration tools" [dependencies] clap = "4" ctrlc = { version = "3", default-features = false } +dunce = "1.0" env_logger = { version = "0.11", default-features = false } log = "0.4" nix = { version = "0.28", optional = true, default-features = false, features = ["signal"] } diff --git a/examples/auto_exec.rs b/examples/auto_exec.rs new file mode 100644 index 0000000..47e8393 --- /dev/null +++ b/examples/auto_exec.rs @@ -0,0 +1,28 @@ +use clap::Parser; +use stacked_errors::Result; +use super_orchestrator::{ctrlc_init, docker_helpers::auto_exec, std_init}; + +/// Runs `super_orchestrator::docker_helpers::auto_exec` +#[derive(Parser, Debug)] +#[command(about)] +struct Args { + /// Prefix of the name of the container + #[arg(short, long)] + prefix: String, + /// Adds the `-t` argument to use a TTY, may be needed on Windows to get + /// around issues with carriage returns being passed. + #[arg(short, long, default_value_t = false)] + tty: bool, +} + +#[tokio::main] +async fn main() -> Result<()> { + std_init()?; + ctrlc_init()?; + let args = Args::parse(); + auto_exec(if args.tty { ["-it"] } else { ["-i"] }, &args.prefix, [ + "sh", + ]) + .await?; + Ok(()) +} diff --git a/examples/auto_exec_i.rs b/examples/auto_exec_i.rs deleted file mode 100644 index ab984fc..0000000 --- a/examples/auto_exec_i.rs +++ /dev/null @@ -1,21 +0,0 @@ -use clap::Parser; -use stacked_errors::Result; -use super_orchestrator::{ctrlc_init, docker_helpers::auto_exec_i, std_init}; - -/// Runs auto_exec_i -#[derive(Parser, Debug)] -#[command(about)] -struct Args { - /// Name of the container - #[arg(short, long)] - container_name: String, -} - -#[tokio::main] -async fn main() -> Result<()> { - std_init()?; - ctrlc_init()?; - let args = Args::parse(); - auto_exec_i(&args.container_name).await?; - Ok(()) -} diff --git a/examples/docker_entrypoint_pattern.rs b/examples/docker_entrypoint_pattern.rs index ca104be..c2c28c4 100644 --- a/examples/docker_entrypoint_pattern.rs +++ b/examples/docker_entrypoint_pattern.rs @@ -1,10 +1,11 @@ //! Note: change working directory to this crate's root in two separate //! terminals. In one terminal, run -//! `cargo r --example auto_exec_i -- --container-name container0` -//! and in the other `cargo r --example docker_entrypoint_pattern`. The first -//! terminal should catch a running container, and you can run commands on it or -//! ctrl-c to end the container early. The second will finish after building and -//! 20 seconds. +//! `cargo r --example auto_exec -- --container-name container0` +//! and in the other `cargo r --example docker_entrypoint_pattern`. Note that on +//! windows you will need to use WSL 2 or else the cross compilation will fail +//! at linking stage. The first terminal should catch a running container, and +//! you can run commands on it or ctrl-c to end the container early. The second +//! will finish after building and 20 seconds. use std::time::Duration; diff --git a/src/command.rs b/src/command.rs index c2b872f..be2d19e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -406,7 +406,7 @@ impl Command { let mut args: Vec = vec![]; for (i, part) in program_with_args.as_ref().split_whitespace().enumerate() { if i == 0 { - program = part.to_owned(); + part.clone_into(&mut program) } else { args.push(part.into()); } diff --git a/src/docker.rs b/src/docker.rs index f7778cd..36463f5 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -83,6 +83,8 @@ pub struct Container { /// Passed as `--volume` to the create args, but these have the advantage of /// being canonicalized and prechecked pub volumes: Vec<(String, String)>, + /// Working directory inside the container + pub workdir: Option, /// Environment variable pairs passed to docker pub environment_vars: Vec<(String, String)>, /// When set, this indicates that the container should run an entrypoint @@ -105,6 +107,7 @@ impl Container { build_args: vec![], create_args: vec![], volumes: vec![], + workdir: None, environment_vars: vec![], entrypoint_file: None, entrypoint_args: vec![], @@ -224,6 +227,12 @@ impl Container { self } + /// Sets the working directory inside the container + pub fn workdir>(mut self, workdir: S) -> Self { + self.workdir = Some(workdir.as_ref().to_string()); + self + } + /// Add arguments to be passed to the entrypoint pub fn entrypoint_args(mut self, entrypoint_args: I) -> Self where @@ -683,6 +692,11 @@ impl ContainerNetwork { &full_name, ]; + if let Some(workdir) = container.workdir.as_ref() { + args.push("-w"); + args.push(workdir) + } + let mut tmp = vec![]; for var in &container.environment_vars { tmp.push(format!("{}={}", var.0, var.1)); diff --git a/src/docker_helpers.rs b/src/docker_helpers.rs index 75e4d4f..4c7fb34 100644 --- a/src/docker_helpers.rs +++ b/src/docker_helpers.rs @@ -46,16 +46,36 @@ pub async fn wait_get_ip_addr( } /// Intended to be called from the main() of a standalone binary, or run from -/// this repo `cargo r --example auto_exec_i -- --container-name main` +/// this repo `cargo r --example auto_exec -- --container-name main` /// /// This actively looks for a running container with the given /// `container_name` prefix, and when such a container starts it gets the -/// container id and runs `docker exec -i [id] bash`, forwarding stdin and -/// stdout to whatever program is calling this. Using Ctrl-C causes this to -/// force terminate the container and resume looping. Ctrl-C again terminates -/// the whole program. -pub async fn auto_exec_i(container_name: &str) -> Result<()> { - info!("running auto_exec_i({container_name})"); +/// container id and runs `docker exec [exec_args..] [id] [container_args..`, +/// forwarding stdin and stdout to whatever program is calling this. Using +/// Ctrl-C causes this to force terminate the container and resume looping. +/// Ctrl-C again terminates the whole program. See the crate examples for more. +pub async fn auto_exec( + exec_args: I0, + container_name: S2, + container_args: I1, +) -> Result<()> +where + I0: IntoIterator, + S0: AsRef, + I1: IntoIterator, + S1: AsRef, + S2: AsRef, +{ + let container_name = container_name.as_ref().to_string(); + let exec_args: Vec = exec_args + .into_iter() + .map(|s| s.as_ref().to_string()) + .collect(); + let container_args: Vec = container_args + .into_iter() + .map(|s| s.as_ref().to_string()) + .collect(); + info!("running auto_exec({exec_args:?} {container_name} {container_args:?})"); loop { if ctrlc_issued_reset() { break @@ -68,7 +88,7 @@ pub async fn auto_exec_i(container_name: &str) -> Result<()> { let mut name_id = None; for line in comres.stdout_as_utf8().stack()?.lines().skip(1) { let line = line.trim(); - if let Some(inx) = line.rfind(container_name) { + if let Some(inx) = line.rfind(&container_name) { let name = &line[inx..]; // make sure we are just getting the last field with the name if name.split_whitespace().nth(1).is_none() { @@ -88,7 +108,10 @@ pub async fn auto_exec_i(container_name: &str) -> Result<()> { "Found container {name} with id {id}, forwarding stdin, stdout, stderr.\nIP is: \ {ip:?}" ); - docker_exec_i(&id).await.stack()?; + let mut total_args = exec_args.clone(); + total_args.push(id.to_string()); + total_args.extend(container_args.clone()); + docker_exec(total_args).await.stack()?; let _ = sh(["docker rm -f", &id]).await; info!("\nTerminated container {id}\n"); } @@ -97,9 +120,13 @@ pub async fn auto_exec_i(container_name: &str) -> Result<()> { Ok(()) } -pub async fn docker_exec_i(container_id: &str) -> Result<()> { - let mut runner = Command::new("docker exec -i") - .args([container_id, "bash"]) +pub async fn docker_exec(args: I) -> Result<()> +where + I: IntoIterator, + S: AsRef, +{ + let mut runner = Command::new("docker exec") + .args(args.into_iter().map(|s| s.as_ref().to_string())) .debug(true) .run_with_stdin(Stdio::inherit()) .await diff --git a/src/paths.rs b/src/paths.rs index 0a7f04d..7e01770 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,8 +1,13 @@ +#![allow(clippy::assigning_clones)] + use std::path::{Path, PathBuf}; use stacked_errors::{Error, Result, StackableErr}; use tokio::fs; +// Note: we use `dunce::simplify` because of https://github.com/rust-lang/rust/issues/42869 +// and because we want to use `tokio::fs`. + /// Canonicalizes and checks the existence of a path. Also adds on better /// information to errors. /// @@ -11,9 +16,14 @@ pub async fn acquire_path(path: impl AsRef) -> Result { let path = path.as_ref(); // note: we don't need fs::try_exists because the canonicalization deals with // testing for existence and the symbolic links - fs::canonicalize(path) + + let mut path = fs::canonicalize(path) .await - .stack_err(|| format!("acquire_path(path: {:?})", path)) + .stack_err(|| format!("acquire_path(path: {:?})", path))?; + if cfg!(windows) { + path = dunce::simplified(&path).to_owned(); + } + Ok(path) } /// Canonicalizes and checks the existence of a file path. Also adds on better @@ -22,9 +32,12 @@ pub async fn acquire_path(path: impl AsRef) -> Result { /// Note: this does not prevent TOCTOU bugs. See the crate examples for more. pub async fn acquire_file_path(file_path: impl AsRef) -> Result { let file_path = file_path.as_ref(); - let path = fs::canonicalize(file_path) + let mut path = fs::canonicalize(file_path) .await .stack_err(|| format!("acquire_file_path(file_path: {:?})", file_path))?; + if cfg!(windows) { + path = dunce::simplified(&path).to_owned(); + } if path.is_file() { Ok(path) } else { @@ -41,9 +54,12 @@ pub async fn acquire_file_path(file_path: impl AsRef) -> Result { /// Note: this does not prevent TOCTOU bugs. See the crate examples for more. pub async fn acquire_dir_path(dir_path: impl AsRef) -> Result { let dir_path = dir_path.as_ref(); - let path = fs::canonicalize(dir_path) + let mut path = fs::canonicalize(dir_path) .await .stack_err(|| format!("acquire_dir_path(dir_path: {:?})", dir_path))?; + if cfg!(windows) { + path = dunce::simplified(&path).to_owned(); + } if path.is_dir() { Ok(path) } else { From 58739cf129eccf7a274720e6f0db8f99976a439f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 6 Apr 2024 17:45:53 -0500 Subject: [PATCH 3/4] remove `std_init`, use `tracing` instead of `log` --- CHANGELOG.md | 2 ++ Cargo.toml | 5 ++--- examples/auto_exec.rs | 4 ++-- examples/basic_commands.rs | 6 +++--- examples/basic_containers.rs | 4 ++-- examples/clean.rs | 5 ++--- examples/commands.rs | 6 ++---- examples/docker_entrypoint_pattern.rs | 6 +++--- examples/postgres.rs | 6 +++--- src/command.rs | 4 ++-- src/docker.rs | 2 +- src/docker_helpers.rs | 2 +- src/misc.rs | 8 -------- 13 files changed, 25 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ece7f..f885923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Changes - Replaced `auto_exec_i` with `auto_exec` which allows customizing arguments +- Removed the `log` dependency in favor of `tracing` +- Removed `std_init` ### Additions - Added a `workdir` option to `Container` diff --git a/Cargo.toml b/Cargo.toml index baff247..fc728d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,8 @@ keywords = ["container", "docker"] description = "programmable container orchestration tools" [dependencies] -clap = "4" ctrlc = { version = "3", default-features = false } dunce = "1.0" -env_logger = { version = "0.11", default-features = false } -log = "0.4" nix = { version = "0.28", optional = true, default-features = false, features = ["signal"] } owo-colors = { version = "4.0", default-features = false } postcard = { version = "1", features = ["use-std"] } @@ -26,10 +23,12 @@ stacked_errors = { version = "0.5", default-features = false } #stacked_errors = { git = "https://github.com/AaronKutch/stacked_errors", rev = "74d52fd24ff7ec1faab4f2065f37ff356f089137", default-features = false } #stacked_errors = { path = "../stacked_errors", default-features = false } tokio = { version = "1", features = ["full"] } +tracing = "0.1" uuid = { version = "1", features = ["v4"] } [dev-dependencies] clap = { version = "4", features = ["derive", "env"] } +tracing-subscriber = "0.3" [features] default = [] diff --git a/examples/auto_exec.rs b/examples/auto_exec.rs index 47e8393..3c36522 100644 --- a/examples/auto_exec.rs +++ b/examples/auto_exec.rs @@ -1,6 +1,6 @@ use clap::Parser; use stacked_errors::Result; -use super_orchestrator::{ctrlc_init, docker_helpers::auto_exec, std_init}; +use super_orchestrator::{ctrlc_init, docker_helpers::auto_exec}; /// Runs `super_orchestrator::docker_helpers::auto_exec` #[derive(Parser, Debug)] @@ -17,7 +17,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); ctrlc_init()?; let args = Args::parse(); auto_exec(if args.tty { ["-it"] } else { ["-i"] }, &args.prefix, [ diff --git a/examples/basic_commands.rs b/examples/basic_commands.rs index d0b9f35..b8022aa 100644 --- a/examples/basic_commands.rs +++ b/examples/basic_commands.rs @@ -2,14 +2,14 @@ use std::time::Duration; use stacked_errors::{ensure, ensure_eq, StackableErr}; use super_orchestrator::{ - sh, stacked_errors::Result, std_init, Command, CommandResult, CommandResultNoDebug, FileOptions, + sh, stacked_errors::Result, Command, CommandResult, CommandResultNoDebug, FileOptions, }; use tokio::time::sleep; #[tokio::main] async fn main() -> Result<()> { - // logging to detect bad drops - std_init()?; + // tracing to detect bad drops + tracing_subscriber::fmt().init(); println!("example 0\n"); diff --git a/examples/basic_containers.rs b/examples/basic_containers.rs index 333140c..d9e3aa2 100644 --- a/examples/basic_containers.rs +++ b/examples/basic_containers.rs @@ -4,14 +4,14 @@ use stacked_errors::{ensure, ensure_eq, Result, StackableErr}; use super_orchestrator::{ docker::{Container, ContainerNetwork, Dockerfile}, net_message::wait_for_ok_lookup_host, - std_init, FileOptions, + FileOptions, }; const TIMEOUT: Duration = Duration::from_secs(300); #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); let logs_dir = "./logs"; println!("\n\nexample 0\n"); diff --git a/examples/clean.rs b/examples/clean.rs index b1d2e3e..a008770 100644 --- a/examples/clean.rs +++ b/examples/clean.rs @@ -1,13 +1,12 @@ use stacked_errors::{ensure, StackableErr}; use super_orchestrator::{ - acquire_dir_path, acquire_file_path, remove_files_in_dir, stacked_errors::Result, std_init, - FileOptions, + acquire_dir_path, acquire_file_path, remove_files_in_dir, stacked_errors::Result, FileOptions, }; use tokio::fs; #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); // note: in regular use you would use `.await.stack()?` on the ends // to tell what lines are failing diff --git a/examples/commands.rs b/examples/commands.rs index 39442f3..c5dca65 100644 --- a/examples/commands.rs +++ b/examples/commands.rs @@ -2,9 +2,7 @@ use std::iter; use clap::Parser; use stacked_errors::{ensure, ensure_eq, StackableErr}; -use super_orchestrator::{ - remove_files_in_dir, stacked_errors::Result, std_init, Command, FileOptions, -}; +use super_orchestrator::{remove_files_in_dir, stacked_errors::Result, Command, FileOptions}; // this program calls itself to get stdout and stderr examples #[derive(Parser, Debug)] @@ -20,7 +18,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); let args = Args::parse(); if args.print { diff --git a/examples/docker_entrypoint_pattern.rs b/examples/docker_entrypoint_pattern.rs index c2c28c4..dd7fa7e 100644 --- a/examples/docker_entrypoint_pattern.rs +++ b/examples/docker_entrypoint_pattern.rs @@ -10,14 +10,14 @@ use std::time::Duration; use clap::Parser; -use log::info; use stacked_errors::{ensure_eq, Error, Result, StackableErr}; use super_orchestrator::{ docker::{Container, ContainerNetwork, Dockerfile}, net_message::NetMessenger, - sh, std_init, FileOptions, + sh, FileOptions, }; use tokio::time::sleep; +use tracing::info; const TIMEOUT: Duration = Duration::from_secs(300); const STD_TRIES: u64 = 300; @@ -46,7 +46,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); let args = Args::parse(); if let Some(ref s) = args.entry_name { diff --git a/examples/postgres.rs b/examples/postgres.rs index da72e85..96c7219 100644 --- a/examples/postgres.rs +++ b/examples/postgres.rs @@ -7,15 +7,15 @@ use std::time::Duration; use clap::Parser; -use log::info; use super_orchestrator::{ acquire_dir_path, docker::{Container, ContainerNetwork, Dockerfile}, sh, stacked_errors::{Error, Result, StackableErr}, - std_init, wait_for_ok, Command, + wait_for_ok, Command, }; use tokio::{fs, time::sleep}; +use tracing::info; // time until the program ends after everything is deployed const END_TIMEOUT: Duration = Duration::from_secs(1_000_000_000); @@ -52,7 +52,7 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - std_init()?; + tracing_subscriber::fmt().init(); let args = Args::parse(); if let Some(ref s) = args.entry_name { diff --git a/src/command.rs b/src/command.rs index be2d19e..fda76e5 100644 --- a/src/command.rs +++ b/src/command.rs @@ -11,7 +11,6 @@ use std::{ time::Duration, }; -use log::warn; use owo_colors::AnsiColors; use serde::{Deserialize, Serialize}; use stacked_errors::{DisplayStr, Error, Result, StackableErr}; @@ -23,6 +22,7 @@ use tokio::{ task::{self, JoinHandle}, time::{sleep, timeout}, }; +use tracing::warn; use crate::{acquire_dir_path, next_terminal_color, FileOptions}; @@ -131,7 +131,7 @@ impl Debug for Command { /// to make a quick copy or other operation, because the task to record program /// outputs needs the lock to progress. /// -/// If the `log` crate is used and an implementor is active, warnings from +/// If the `tracing` crate is used and a subscriber is active, warnings from /// bad `Drop`s can be issued /// /// The `Default` impl is for if an empty runner not attached to anything is diff --git a/src/docker.rs b/src/docker.rs index 36463f5..94a40b7 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -8,10 +8,10 @@ use std::{ time::Duration, }; -use log::{info, warn}; use serde::{Deserialize, Serialize}; use stacked_errors::{Error, Result, StackableErr}; use tokio::time::{sleep, Instant}; +use tracing::{info, warn}; use uuid::Uuid; use crate::{ diff --git a/src/docker_helpers.rs b/src/docker_helpers.rs index 4c7fb34..d918c68 100644 --- a/src/docker_helpers.rs +++ b/src/docker_helpers.rs @@ -1,8 +1,8 @@ use std::{net::IpAddr, process::Stdio, time::Duration}; -use log::{info, warn}; use stacked_errors::{Error, Result, StackableErr}; use tokio::time::sleep; +use tracing::{info, warn}; use crate::{ctrlc_issued_reset, sh, stacked_get, wait_for_ok, Command}; diff --git a/src/misc.rs b/src/misc.rs index 523d2ee..1c9b472 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -31,14 +31,6 @@ pub fn ctrlc_init() -> Result<()> { Ok(()) } -/// Sets up `env_logger` with `LevelFilter::Info` -pub fn std_init() -> Result<()> { - env_logger::Builder::new() - .filter_level(log::LevelFilter::Info) - .init(); - Ok(()) -} - /// Returns if `CTRLC_ISSUED` has been set, and resets it to `false` pub fn ctrlc_issued_reset() -> bool { CTRLC_ISSUED.swap(false, Ordering::SeqCst) From 935934acd1ab35b89787e24fe1e12a7f4c493d0f Mon Sep 17 00:00:00 2001 From: Aaron Kutch Date: Sat, 6 Apr 2024 17:48:53 -0500 Subject: [PATCH 4/4] Version 0.11.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fc728d2..d8a5719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "super_orchestrator" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = ["Aaron Kutch "] license = "MIT OR Apache-2.0"