diff --git a/examples/termination/main.rs b/examples/termination/main.rs index d3b2bf9c..797aa6d6 100755 --- a/examples/termination/main.rs +++ b/examples/termination/main.rs @@ -46,6 +46,7 @@ fn system_main() -> AnyhowResult<()> { "--variable".into(), format!("RESOURCE:{resource_file}"), ], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Complete, }; let token = CancellationToken::new(); @@ -100,6 +101,7 @@ fn rcc_main(rcc_binary_path: Utf8PathBuf) -> AnyhowResult<()> { "--variable".into(), format!("RESOURCE:{resource_file}"), ], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Complete, }; let rcc_environment = Environment::Rcc(RCCEnvironment { diff --git a/src/bin/scheduler/internal_config.rs b/src/bin/scheduler/internal_config.rs index ac2bae7a..a21d94ad 100644 --- a/src/bin/scheduler/internal_config.rs +++ b/src/bin/scheduler/internal_config.rs @@ -133,6 +133,9 @@ pub fn from_external_config( .map(|f| plan_source_dir.join(f)) .collect(), exit_on_failure: plan_config.robot_config.exit_on_failure, + environment_variables_rendered_obfuscated: plan_config + .robot_config + .environment_variables_rendered_obfuscated, }, plan_config.execution_config.n_attempts_max, plan_config.execution_config.retry_strategy, @@ -200,6 +203,7 @@ mod tests { variable_files: vec![], argument_files: vec!["args.txt".into(), "more_args.txt".into()], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, @@ -235,6 +239,7 @@ mod tests { variable_files: vec!["vars.txt".into()], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, @@ -317,6 +322,7 @@ mod tests { "--variablefile".into(), "/synthetic_tests/rcc/vars.txt".into() ], + envs_rendered_obfuscated: vec![], n_attempts_max: 1, retry_strategy: RetryStrategy::Complete, } @@ -378,6 +384,7 @@ mod tests { "--argumentfile".into(), "/synthetic_tests/system/more_args.txt".into() ], + envs_rendered_obfuscated: vec![], n_attempts_max: 1, retry_strategy: RetryStrategy::Incremental, } diff --git a/src/command_spec.rs b/src/command_spec.rs index 9b3ca967..fb261bf2 100644 --- a/src/command_spec.rs +++ b/src/command_spec.rs @@ -7,18 +7,29 @@ use std::process::Command; pub struct CommandSpec { pub executable: String, pub arguments: Vec, - pub envs: Vec<(String, String)>, + pub envs_rendered_plain: Vec<(String, String)>, + pub envs_rendered_obfuscated: Vec<(String, String)>, } impl Display for CommandSpec { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let env_str = self - .envs + let rendered_plain_envs_iter = self + .envs_rendered_plain .iter() - .map(|(k, _)| format!("{k}=***")) - .collect::>() - .join(" "); - write!(f, "{env_str} {}", self.to_command_string()) + .map(|(k, v)| format!("{k}=\"{v}\"")); + let rendered_obfuscated_envs_iter = self + .envs_rendered_obfuscated + .iter() + .map(|(k, _)| format!("{k}=***")); + write!( + f, + "{env_str} {cmd_string}", + env_str = rendered_plain_envs_iter + .chain(rendered_obfuscated_envs_iter) + .collect::>() + .join(" "), + cmd_string = self.to_command_string() + ) } } @@ -28,8 +39,9 @@ impl From<&CommandSpec> for Command { command.args(&command_spec.arguments); command.envs( command_spec - .envs + .envs_rendered_plain .iter() + .chain(command_spec.envs_rendered_obfuscated.iter()) .map(|(k, v)| (OsString::from(&k), OsString::from(&v))), ); command @@ -47,7 +59,8 @@ impl CommandSpec { Self { executable: executable.as_ref().into(), arguments: vec![], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], } } @@ -65,8 +78,21 @@ impl CommandSpec { self } - pub fn add_env(&mut self, key: String, value: String) -> &mut Self { - self.envs.push((key, value)); + pub fn add_plain_env(&mut self, key: T, value: T) -> &mut Self + where + T: AsRef, + { + self.envs_rendered_plain + .push((key.as_ref().into(), value.as_ref().into())); + self + } + + pub fn add_obfuscated_env(&mut self, key: T, value: T) -> &mut Self + where + T: AsRef, + { + self.envs_rendered_obfuscated + .push((key.as_ref().into(), value.as_ref().into())); self } @@ -91,10 +117,11 @@ mod tests { String::from("--option"), String::from("value"), ], - envs: vec![("RCC_REMOTE_ORIGIN".into(), "http://1.com".into())], + envs_rendered_plain: vec![("ROBOCORP_HOME".into(), "/opt/rc_home".into())], + envs_rendered_obfuscated: vec![("RCC_REMOTE_ORIGIN".into(), "http://1.com".into())], }; let expected = - "RCC_REMOTE_ORIGIN=*** \"/my/binary\" \"mandatory\" \"--flag\" \"--option\" \"value\""; + "ROBOCORP_HOME=\"/opt/rc_home\" RCC_REMOTE_ORIGIN=*** \"/my/binary\" \"mandatory\" \"--flag\" \"--option\" \"value\""; assert_eq!(format!("{command_spec}"), expected); } @@ -106,7 +133,8 @@ mod tests { .arg("--flag") .arg("--option") .arg("value") - .env("key", "val"); + .env("plain_key", "plain_val") + .env("obfuscated_key", "obfuscated_val"); let command = Command::from(&CommandSpec { executable: String::from("/my/binary"), arguments: vec![ @@ -115,7 +143,11 @@ mod tests { String::from("--option"), String::from("value"), ], - envs: vec![(String::from("key"), String::from("val"))], + envs_rendered_plain: vec![(String::from("plain_key"), String::from("plain_val"))], + envs_rendered_obfuscated: vec![( + String::from("obfuscated_key"), + String::from("obfuscated_val"), + )], }); assert_eq!(command.get_program(), expected.get_program()); assert_eq!( @@ -135,7 +167,8 @@ mod tests { CommandSpec { executable: String::from("/my/binary"), arguments: vec![], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], } ) } @@ -145,7 +178,8 @@ mod tests { let mut command_spec = CommandSpec { executable: String::from("/my/binary"), arguments: vec![], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], }; command_spec.add_argument("arg"); assert_eq!( @@ -153,17 +187,28 @@ mod tests { CommandSpec { executable: String::from("/my/binary"), arguments: vec!["arg".into()], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], } ); } #[test] - fn add_env() { + fn add_plain_env() { + let mut command_spec = CommandSpec::new("/my/binary"); + command_spec.add_plain_env("key", "val"); + assert_eq!( + command_spec.envs_rendered_plain, + [(String::from("key"), String::from("val"))] + ); + } + + #[test] + fn add_obfuscated_env() { let mut command_spec = CommandSpec::new("/my/binary"); - command_spec.add_env("key".to_string(), "val".to_string()); + command_spec.add_obfuscated_env("key", "val"); assert_eq!( - command_spec.envs, + command_spec.envs_rendered_obfuscated, [(String::from("key"), String::from("val"))] ); } @@ -173,7 +218,8 @@ mod tests { let mut command_spec = CommandSpec { executable: String::from("/my/binary"), arguments: vec![], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], }; command_spec.add_arguments(vec!["arg1", "arg2"]); assert_eq!( @@ -181,7 +227,8 @@ mod tests { CommandSpec { executable: String::from("/my/binary"), arguments: vec!["arg1".into(), "arg2".into()], - envs: vec![], + envs_rendered_plain: vec![], + envs_rendered_obfuscated: vec![], } ); } diff --git a/src/config.rs b/src/config.rs index fa2e20d3..b2baae10 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,6 +78,7 @@ pub struct RobotConfig { pub variable_files: Vec, pub argument_files: Vec, pub exit_on_failure: bool, + pub environment_variables_rendered_obfuscated: Vec, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -86,6 +87,12 @@ pub struct RobotFrameworkVariable { pub value: String, } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct RobotFrameworkObfuscatedEnvVar { + pub name: String, + pub value: String, +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ExecutionConfig { pub n_attempts_max: usize, diff --git a/src/environment.rs b/src/environment.rs index a34d101e..a17e0346 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -100,7 +100,7 @@ impl RCCEnvironment { pub fn bundled_command_spec(binary_path: &Utf8Path, robocorp_home: String) -> CommandSpec { let mut command_spec = CommandSpec::new(binary_path); command_spec.add_argument("--bundled"); - command_spec.add_env("ROBOCORP_HOME".to_string(), robocorp_home); + command_spec.add_obfuscated_env("ROBOCORP_HOME", &robocorp_home); command_spec } @@ -121,8 +121,7 @@ impl RCCEnvironment { .add_argument("script"); self.apply_current_settings(&mut build_command_spec); if let Some(remote_origin) = &self.remote_origin { - build_command_spec - .add_env(String::from("RCC_REMOTE_ORIGIN"), remote_origin.to_string()); + build_command_spec.add_obfuscated_env("RCC_REMOTE_ORIGIN", remote_origin); } build_command_spec.add_argument("--").add_argument( #[cfg(unix)] @@ -221,7 +220,7 @@ mod tests { #[cfg(windows)] "cmd.exe", ) - .add_env(String::from("ROBOCORP_HOME"), String::from("~/.robocorp/")); + .add_obfuscated_env("ROBOCORP_HOME", "~/.robocorp/"); assert_eq!( RCCEnvironment { @@ -284,7 +283,7 @@ mod tests { .add_argument("--flag") .add_argument("--option") .add_argument("option_value") - .add_env(String::from("ROBOCORP_HOME"), String::from("~/.robocorp/")); + .add_obfuscated_env("ROBOCORP_HOME", "~/.robocorp/"); assert_eq!( RCCEnvironment { binary_path: Utf8PathBuf::from("C:\\bin\\z.exe"), diff --git a/src/rf/robot.rs b/src/rf/robot.rs index c538550f..9bcf7657 100644 --- a/src/rf/robot.rs +++ b/src/rf/robot.rs @@ -9,6 +9,7 @@ pub const PYTHON_EXECUTABLE: &str = "python"; pub struct Robot { pub robot_target: Utf8PathBuf, pub command_line_args: Vec, + pub envs_rendered_obfuscated: Vec<(String, String)>, pub n_attempts_max: usize, pub retry_strategy: RetryStrategy, } @@ -28,6 +29,11 @@ impl Robot { ) -> Self { Self { robot_target: robot_config.robot_target.clone(), + envs_rendered_obfuscated: robot_config + .environment_variables_rendered_obfuscated + .iter() + .map(|var| (var.name.clone(), var.value.clone())) + .collect(), command_line_args: Self::config_to_command_line_args(robot_config), n_attempts_max, retry_strategy, @@ -74,6 +80,9 @@ impl Robot { .add_argument("--report") .add_argument("NONE") .add_argument(&self.robot_target); + for (k, v) in &self.envs_rendered_obfuscated { + command_spec.add_obfuscated_env(k, v); + } command_spec } @@ -121,10 +130,10 @@ impl Robot { #[cfg(test)] mod tests { use super::*; - use crate::config::RobotFrameworkVariable; + use crate::config::{RobotFrameworkObfuscatedEnvVar, RobotFrameworkVariable}; #[test] - fn test_command_line_args_empty() { + fn test_new_command_line_args_empty() { assert!(Robot::new( RobotConfig { robot_target: "/suite/tasks.robot".into(), @@ -137,6 +146,7 @@ mod tests { variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![] }, 1, RetryStrategy::Incremental @@ -146,7 +156,7 @@ mod tests { } #[test] - fn test_command_line_args_non_empty() { + fn test_new_command_line_args_non_empty() { assert_eq!( Robot::new( RobotConfig { @@ -175,6 +185,7 @@ mod tests { "/suite/argfile2.txt".into() ], exit_on_failure: true, + environment_variables_rendered_obfuscated: vec![], }, 1, RetryStrategy::Incremental @@ -216,6 +227,36 @@ mod tests { ); } + #[test] + fn test_new_obfuscated_env_vars() { + assert_eq!( + Robot::new( + RobotConfig { + robot_target: "/suite/tasks.robot".into(), + top_level_suite_name: None, + suites: vec![], + tests: vec![], + test_tags_include: vec![], + test_tags_exclude: vec![], + variables: vec![], + variable_files: vec![], + argument_files: vec![], + exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![ + RobotFrameworkObfuscatedEnvVar { + name: "NAME".into(), + value: "value".into() + } + ] + }, + 1, + RetryStrategy::Incremental + ) + .envs_rendered_obfuscated, + vec![("NAME".into(), "value".into())] + ); + } + #[test] fn create_complete_command_spec() { // Assemble @@ -228,6 +269,7 @@ mod tests { "--variable".into(), "k:v".into(), ], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Complete, }; let output_directory = @@ -267,6 +309,7 @@ mod tests { "top_suite".into(), "--exitonfailure".into(), ], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Incremental, }; let output_directory = @@ -301,6 +344,7 @@ mod tests { robot_target: "~/calculator_test/calculator.robot".into(), n_attempts_max: 2, command_line_args: vec![], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Incremental, }; let output_directory = @@ -327,6 +371,26 @@ mod tests { assert_eq!(command_spec, expected) } + #[test] + fn create_command_obfuscated_env_vars() { + assert_eq!( + Robot { + robot_target: "~/calculator_test/calculator.robot".into(), + n_attempts_max: 1, + command_line_args: vec![], + envs_rendered_obfuscated: vec![("NAME".into(), "value".into())], + retry_strategy: RetryStrategy::Complete, + } + .command_spec( + &Utf8PathBuf::default(), + &Utf8PathBuf::default().join("out.xml"), + 1 + ) + .envs_rendered_obfuscated, + vec![("NAME".into(), "value".into())] + ) + } + #[test] fn create_two_attempts() { // Assemble @@ -334,6 +398,7 @@ mod tests { robot_target: "~/calculator_test/calculator.robot".into(), n_attempts_max: 2, command_line_args: vec![], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Incremental, }; let output_directory = diff --git a/src/tasks/windows.rs b/src/tasks/windows.rs index 84662485..1c763409 100644 --- a/src/tasks/windows.rs +++ b/src/tasks/windows.rs @@ -236,8 +236,9 @@ impl TaskManager { fn build_task_script(task_name: &str, command_spec: &CommandSpec, paths: &Paths) -> String { let set_envs = command_spec - .envs + .envs_rendered_plain .iter() + .chain(command_spec.envs_rendered_obfuscated.iter()) .map(|(k, v)| format!("set \"{k}={v}\"")) .collect::>() .join("\n"); @@ -330,10 +331,8 @@ mod tests { .add_argument("--some-flag") .add_argument("--some-option") .add_argument("some-value"); - command_spec.add_env( - String::from("RCC_REMOTE_ORIGIN"), - String::from("http://1.com"), - ); + command_spec.add_plain_env("ABC", "123"); + command_spec.add_obfuscated_env("RCC_REMOTE_ORIGIN", "http://1.com"); assert_eq!( build_task_script( "robotmk_task", @@ -343,6 +342,7 @@ mod tests { "@echo off setlocal echo Robotmk: running task robotmk_task. Please do not close this window. +set \"ABC=123\" set \"RCC_REMOTE_ORIGIN=http://1.com\" \"C:\\\\somewhere\\\\rcc.exe\" \"mandatory\" \"--some-flag\" \"--some-option\" \"some-value\" > C:\\working\\plans\\my_plan\\123\\0.stdout 2> C:\\working\\plans\\my_plan\\123\\0.stderr\necho %errorlevel% > C:\\working\\plans\\my_plan\\123\\0.exit_code endlocal" diff --git a/tests/test_ht_import_scheduler.rs b/tests/test_ht_import_scheduler.rs index 70a080b8..04e36f18 100644 --- a/tests/test_ht_import_scheduler.rs +++ b/tests/test_ht_import_scheduler.rs @@ -97,6 +97,7 @@ fn create_config(test_dir: &Utf8Path, suite_dir: &Utf8Path, rcc_config: RCCConfi variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, diff --git a/tests/test_plan_run.rs b/tests/test_plan_run.rs index d5cade7b..51b4db4e 100644 --- a/tests/test_plan_run.rs +++ b/tests/test_plan_run.rs @@ -17,6 +17,7 @@ fn test_rebot_run() -> AnyhowResult<()> { robot_target: "tests/minimal_suite/tasks.robot".into(), n_attempts_max: 1, command_line_args: vec![], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Complete, }; let (attempt_reports, rebot) = run_attempts_with_rebot( @@ -45,6 +46,7 @@ fn test_timeout_process() -> AnyhowResult<()> { robot_target: "tests/timeout/tasks.robot".into(), n_attempts_max: 1, command_line_args: vec!["--variable".into(), format!("RESOURCE:{resource}")], + envs_rendered_obfuscated: vec![], retry_strategy: RetryStrategy::Complete, }; let (attempt_reports, rebot) = run_attempts_with_rebot( diff --git a/tests/test_scheduler.rs b/tests/test_scheduler.rs index 33a72bfa..37ddbc07 100644 --- a/tests/test_scheduler.rs +++ b/tests/test_scheduler.rs @@ -180,6 +180,7 @@ fn create_config( variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, @@ -219,6 +220,7 @@ fn create_config( variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, @@ -262,6 +264,7 @@ fn create_config( variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1, @@ -313,6 +316,7 @@ fn create_config( variable_files: vec![], argument_files: vec![], exit_on_failure: false, + environment_variables_rendered_obfuscated: vec![], }, execution_config: ExecutionConfig { n_attempts_max: 1,