@@ -16,7 +16,10 @@ pub struct CommandLine {
1616
1717impl CommandLine {
1818 pub fn from_vec ( vec : & Vec < String > ) -> anyhow:: Result < Self > {
19+ log:: debug!( "Creating CommandLine from vector: {:?}" , vec) ;
20+
1921 if vec. is_empty ( ) {
22+ log:: error!( "Empty command line vector provided" ) ;
2023 return Err ( anyhow:: anyhow!( "empty command line" ) ) ;
2124 }
2225
@@ -25,29 +28,44 @@ impl CommandLine {
2528 let mut args = Vec :: new ( ) ;
2629
2730 for arg in vec {
31+ log:: trace!( "Processing argument: {}" , arg) ;
2832 if arg == "sudo" {
33+ log:: debug!( "Sudo flag detected" ) ;
2934 sudo = true ;
3035 } else if app. is_empty ( ) {
36+ log:: debug!( "Setting application name: {}" , arg) ;
3137 app = arg. to_string ( ) ;
3238 } else {
39+ log:: trace!( "Adding argument: {}" , arg) ;
3340 args. push ( arg. to_string ( ) ) ;
3441 }
3542 }
3643
3744 if app. is_empty ( ) {
45+ log:: error!( "Could not determine application name from: {:?}" , vec) ;
3846 return Err ( anyhow:: anyhow!(
3947 "could not determine application name from command line: {:?}" ,
4048 vec
4149 ) ) ;
4250 }
4351
4452 let app_in_path = if let Ok ( path) = which:: which ( & app) {
53+ log:: debug!( "Found application in path: {}" , path. display( ) ) ;
4554 app = path. to_string_lossy ( ) . to_string ( ) ;
4655 true
4756 } else {
57+ log:: debug!( "Application '{}' not found in PATH" , app) ;
4858 false
4959 } ;
5060
61+ log:: debug!(
62+ "Created CommandLine: sudo={}, app={}, app_in_path={}, args={:?}" ,
63+ sudo,
64+ app,
65+ app_in_path,
66+ args
67+ ) ;
68+
5169 Ok ( Self {
5270 sudo,
5371 app,
@@ -62,43 +80,93 @@ impl CommandLine {
6280 vec : & Vec < String > ,
6381 env : BTreeMap < String , String > ,
6482 ) -> anyhow:: Result < Self > {
83+ log:: debug!( "creating CommandLine with environment variables" ) ;
84+ log:: trace!( "environment variables: {:?}" , env) ;
6585 let mut cmd = Self :: from_vec ( vec) ?;
6686 cmd. env = env;
6787 Ok ( cmd)
6888 }
6989
90+ fn get_env_interpolated_args ( & self ) -> Vec < String > {
91+ log:: debug!( "interpolating variables from environment: {:?}" , self . env) ;
92+
93+ let args = self
94+ . args
95+ . iter ( )
96+ . map ( |arg| {
97+ let mut result = arg. clone ( ) ;
98+ for ( key, value) in & self . env {
99+ let pattern = format ! ( "${{{}}}" , key) ;
100+ if result. contains ( & pattern) {
101+ log:: debug!( "replacing {} with {}" , pattern, value) ;
102+ result = result. replace ( & pattern, value) ;
103+ }
104+ }
105+ result
106+ } )
107+ . collect ( ) ;
108+
109+ log:: debug!( "after interpolation: {:?}" , & args) ;
110+
111+ args
112+ }
113+
70114 pub async fn execute ( & self , ssh : Option < SSHConnection > ) -> anyhow:: Result < String > {
115+ // execyte via ssh
71116 if let Some ( ssh) = ssh {
72- ssh. execute ( self . sudo , & self . app , & self . args ) . await
73- } else {
74- let output = tokio:: process:: Command :: new ( & self . app )
75- . args ( & self . args )
76- . output ( )
77- . await ?;
117+ return ssh. execute ( self . sudo , & self . app , & self . args ) . await ;
118+ }
78119
79- let mut parts = vec ! [ ] ;
120+ log:: debug!( "executing command: {}" , self ) ;
121+ log:: debug!( "full command details: {:?}" , self ) ;
80122
81- let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
82- let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
123+ let args = self . get_env_interpolated_args ( ) ;
83124
84- if !output. status . success ( ) {
85- parts. push ( format ! ( "EXIT CODE: {}" , & output. status) ) ;
86- }
125+ let mut command = tokio:: process:: Command :: new ( & self . app ) ;
126+ command. args ( & args) ;
87127
88- if !stdout. is_empty ( ) {
89- parts. push ( stdout. to_string ( ) ) ;
90- }
128+ // log environment variables if present
129+ if !self . env . is_empty ( ) {
130+ log:: debug!( "setting environment variables: {:?}" , self . env) ;
131+ command. envs ( & self . env ) ;
132+ }
91133
92- if !stderr . is_empty ( ) {
93- if output. status . success ( ) {
94- parts . push ( stderr . to_string ( ) ) ;
95- } else {
96- parts . push ( format ! ( "ERROR: {}" , stderr ) ) ;
97- }
98- }
134+ let output = command . output ( ) . await ? ;
135+ log :: debug! ( "command completed with status: {:?}" , output. status) ;
136+
137+ let mut parts = vec ! [ ] ;
138+
139+ let stdout = String :: from_utf8_lossy ( & output . stdout ) ;
140+ let stderr = String :: from_utf8_lossy ( & output . stderr ) ;
99141
100- Ok ( parts. join ( "\n " ) )
142+ if !output. status . success ( ) {
143+ log:: warn!( "command failed with exit code: {}" , output. status) ;
144+ parts. push ( format ! ( "EXIT CODE: {}" , & output. status) ) ;
101145 }
146+
147+ if !stdout. is_empty ( ) {
148+ log:: trace!( "command stdout: {}" , stdout) ;
149+ parts. push ( stdout. to_string ( ) ) ;
150+ }
151+
152+ if !stderr. is_empty ( ) {
153+ if output. status . success ( ) {
154+ log:: debug!( "command stderr (success): {}" , stderr) ;
155+ parts. push ( stderr. to_string ( ) ) ;
156+ } else {
157+ log:: error!( "command stderr (failure): {}" , stderr) ;
158+ parts. push ( format ! ( "ERROR: {}" , stderr) ) ;
159+ }
160+ }
161+
162+ let result = parts. join ( "\n " ) ;
163+ log:: debug!(
164+ "command execution completed, output length: {}" ,
165+ result. len( )
166+ ) ;
167+ log:: trace!( "command output: {}" , result) ;
168+
169+ Ok ( result)
102170 }
103171}
104172
@@ -208,4 +276,61 @@ mod tests {
208276 let result = cmd. execute ( None ) . await ;
209277 assert ! ( result. is_err( ) ) ;
210278 }
279+
280+ #[ test]
281+ fn test_get_env_interpolated_args_with_env_vars ( ) {
282+ let mut env = BTreeMap :: new ( ) ;
283+ env. insert ( "TEST_VAR" . to_string ( ) , "test_value" . to_string ( ) ) ;
284+ env. insert ( "OTHER_VAR" . to_string ( ) , "other_value" . to_string ( ) ) ;
285+
286+ let cmd = CommandLine {
287+ sudo : false ,
288+ app : "echo" . to_string ( ) ,
289+ args : vec ! [ "${TEST_VAR}" . to_string( ) , "${OTHER_VAR}" . to_string( ) ] ,
290+ app_in_path : true ,
291+ env,
292+ temp_env_file : None ,
293+ } ;
294+
295+ let result = cmd. get_env_interpolated_args ( ) ;
296+ assert_eq ! ( result, vec![ "test_value" , "other_value" ] ) ;
297+ }
298+
299+ #[ test]
300+ fn test_get_env_interpolated_args_with_missing_vars ( ) {
301+ let env = BTreeMap :: new ( ) ;
302+ let cmd = CommandLine {
303+ sudo : false ,
304+ app : "echo" . to_string ( ) ,
305+ args : vec ! [ "${MISSING_VAR}" . to_string( ) ] ,
306+ app_in_path : true ,
307+ env,
308+ temp_env_file : None ,
309+ } ;
310+
311+ let result = cmd. get_env_interpolated_args ( ) ;
312+ assert_eq ! ( result, vec![ "${MISSING_VAR}" ] ) ;
313+ }
314+
315+ #[ test]
316+ fn test_get_env_interpolated_args_with_mixed_content ( ) {
317+ let mut env = BTreeMap :: new ( ) ;
318+ env. insert ( "VAR" . to_string ( ) , "value" . to_string ( ) ) ;
319+
320+ let cmd = CommandLine {
321+ sudo : false ,
322+ app : "echo" . to_string ( ) ,
323+ args : vec ! [
324+ "prefix_${VAR}" . to_string( ) ,
325+ "normal_arg" . to_string( ) ,
326+ "${VAR}_suffix" . to_string( ) ,
327+ ] ,
328+ app_in_path : true ,
329+ env,
330+ temp_env_file : None ,
331+ } ;
332+
333+ let result = cmd. get_env_interpolated_args ( ) ;
334+ assert_eq ! ( result, vec![ "prefix_value" , "normal_arg" , "value_suffix" ] ) ;
335+ }
211336}
0 commit comments