blob: 4d927714199c9d3936a75a63e794d931109ed229 [file] [log] [blame]
Michael Walshc3b512e2017-02-20 15:59:01 -06001#!/usr/bin/env python
2
3r"""
4This module provides command execution functions such as cmd_fnc and cmd_fnc_u.
5"""
6
Michael Walsh21083d22018-06-01 14:19:32 -05007import os
Michael Walshc3b512e2017-02-20 15:59:01 -06008import sys
9import subprocess
Michael Walshf41fac82017-08-02 15:05:24 -050010import collections
Michael Walsh21083d22018-06-01 14:19:32 -050011import signal
12import time
George Keishing16244c22019-01-31 16:16:14 +000013import re
14import inspect
Michael Walshc3b512e2017-02-20 15:59:01 -060015
Michael Walshc3b512e2017-02-20 15:59:01 -060016import gen_print as gp
17import gen_valid as gv
18import gen_misc as gm
Michael Walshd6901502017-11-14 12:58:37 -060019
20robot_env = gp.robot_env
21
Michael Walshafc53a22017-04-12 15:52:28 -050022if robot_env:
Michael Walsha3e2f532018-01-10 13:43:42 -060023 from robot.libraries.BuiltIn import BuiltIn
Michael Walshc3b512e2017-02-20 15:59:01 -060024
25
Michael Walsh21083d22018-06-01 14:19:32 -050026# cmd_fnc and cmd_fnc_u should now be considered deprecated. shell_cmd and
27# t_shell_cmd should be used instead.
Michael Walshc3b512e2017-02-20 15:59:01 -060028def cmd_fnc(cmd_buf,
29 quiet=None,
30 test_mode=None,
Michael Walshafc53a22017-04-12 15:52:28 -050031 debug=0,
Michael Walshc3b512e2017-02-20 15:59:01 -060032 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -050033 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -060034 return_stderr=0,
35 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -060036 r"""
Michael Walshcfe9fed2017-09-12 17:13:10 -050037 Run the given command in a shell and return the shell return code and the
38 output.
Michael Walshc3b512e2017-02-20 15:59:01 -060039
40 Description of arguments:
41 cmd_buf The command string to be run in a shell.
42 quiet Indicates whether this function should run
Michael Walshcfe9fed2017-09-12 17:13:10 -050043 the print_issuing() function which prints
44 "Issuing: <cmd string>" to stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060045 test_mode If test_mode is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050046 not actually run the command. If
47 print_output is set, it will print
48 "(test_mode) Issuing: <cmd string>" to
49 stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060050 debug If debug is set, this function will print
51 extra debug info.
52 print_output If this is set, this function will print
Michael Walshcfe9fed2017-09-12 17:13:10 -050053 the stdout/stderr generated by the shell
54 command.
Michael Walshc3b512e2017-02-20 15:59:01 -060055 show_err If show_err is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050056 print a standardized error report if the
57 shell command returns non-zero.
58 return_stderr If return_stderr is set, this function
59 will process the stdout and stderr streams
60 from the shell command separately. It
61 will also return stderr in addition to the
62 return code and the stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060063 """
64
Michael Walshcfe9fed2017-09-12 17:13:10 -050065 # Determine default values.
Michael Walshc3b512e2017-02-20 15:59:01 -060066 quiet = int(gm.global_default(quiet, 0))
67 test_mode = int(gm.global_default(test_mode, 0))
Michael Walshc3b512e2017-02-20 15:59:01 -060068
69 if debug:
Michael Walshafc53a22017-04-12 15:52:28 -050070 gp.print_vars(cmd_buf, quiet, test_mode, debug)
Michael Walshc3b512e2017-02-20 15:59:01 -060071
72 err_msg = gv.svalid_value(cmd_buf)
73 if err_msg != "":
74 raise ValueError(err_msg)
75
76 if not quiet:
Michael Walshafc53a22017-04-12 15:52:28 -050077 gp.pissuing(cmd_buf, test_mode)
Michael Walshc3b512e2017-02-20 15:59:01 -060078
79 if test_mode:
Michael Walshcfe9fed2017-09-12 17:13:10 -050080 if return_stderr:
81 return 0, "", ""
82 else:
83 return 0, ""
84
85 if return_stderr:
86 err_buf = ""
87 stderr = subprocess.PIPE
88 else:
89 stderr = subprocess.STDOUT
Michael Walshc3b512e2017-02-20 15:59:01 -060090
91 sub_proc = subprocess.Popen(cmd_buf,
92 bufsize=1,
93 shell=True,
Michael Walsh3ba8ecd2018-04-24 11:33:25 -050094 executable='/bin/bash',
Michael Walshc3b512e2017-02-20 15:59:01 -060095 stdout=subprocess.PIPE,
Michael Walshcfe9fed2017-09-12 17:13:10 -050096 stderr=stderr)
Michael Walshc3b512e2017-02-20 15:59:01 -060097 out_buf = ""
Michael Walshcfe9fed2017-09-12 17:13:10 -050098 if return_stderr:
99 for line in sub_proc.stderr:
George Keishing36efbc02018-12-12 10:18:23 -0600100 try:
101 err_buf += line
102 except TypeError:
103 line = line.decode("utf-8")
104 err_buf += line
Michael Walshcfe9fed2017-09-12 17:13:10 -0500105 if not print_output:
106 continue
Michael Walshc108e422019-03-28 12:27:18 -0500107 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600108 for line in sub_proc.stdout:
George Keishing36efbc02018-12-12 10:18:23 -0600109 try:
110 out_buf += line
111 except TypeError:
112 line = line.decode("utf-8")
113 out_buf += line
Michael Walshc3b512e2017-02-20 15:59:01 -0600114 if not print_output:
115 continue
Michael Walshc108e422019-03-28 12:27:18 -0500116 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600117 if print_output and not robot_env:
118 sys.stdout.flush()
119 sub_proc.communicate()
120 shell_rc = sub_proc.returncode
Michael Walsha3e2f532018-01-10 13:43:42 -0600121 if shell_rc != 0:
122 err_msg = "The prior shell command failed.\n"
123 err_msg += gp.sprint_var(shell_rc, 1)
Michael Walshcfe9fed2017-09-12 17:13:10 -0500124 if not print_output:
125 err_msg += "out_buf:\n" + out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600126
Michael Walsha3e2f532018-01-10 13:43:42 -0600127 if show_err:
Michael Walshc108e422019-03-28 12:27:18 -0500128 gp.print_error_report(err_msg)
Michael Walsha3e2f532018-01-10 13:43:42 -0600129 if not ignore_err:
130 if robot_env:
131 BuiltIn().fail(err_msg)
132 else:
133 raise ValueError(err_msg)
Michael Walshcfe9fed2017-09-12 17:13:10 -0500134
135 if return_stderr:
136 return shell_rc, out_buf, err_buf
137 else:
138 return shell_rc, out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600139
Michael Walshc3b512e2017-02-20 15:59:01 -0600140
Michael Walshc3b512e2017-02-20 15:59:01 -0600141def cmd_fnc_u(cmd_buf,
142 quiet=None,
143 debug=None,
144 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500145 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -0600146 return_stderr=0,
147 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -0600148 r"""
149 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details.
150
151 Note the "u" in "cmd_fnc_u" stands for "unconditional".
152 """
153
154 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500155 print_output=print_output, show_err=show_err,
Michael Walsha3e2f532018-01-10 13:43:42 -0600156 return_stderr=return_stderr, ignore_err=ignore_err)
Michael Walshc3b512e2017-02-20 15:59:01 -0600157
Michael Walshf41fac82017-08-02 15:05:24 -0500158
Michael Walshf41fac82017-08-02 15:05:24 -0500159def parse_command_string(command_string):
Michael Walshf41fac82017-08-02 15:05:24 -0500160 r"""
161 Parse a bash command-line command string and return the result as a
162 dictionary of parms.
163
164 This can be useful for answering questions like "What did the user specify
165 as the value for parm x in the command string?".
166
167 This function expects the command string to follow the following posix
168 conventions:
169 - Short parameters:
170 -<parm name><space><arg value>
171 - Long parameters:
172 --<parm name>=<arg value>
173
174 The first item in the string will be considered to be the command. All
175 values not conforming to the specifications above will be considered
176 positional parms. If there are multiple parms with the same name, they
177 will be put into a list (see illustration below where "-v" is specified
178 multiple times).
179
180 Description of argument(s):
181 command_string The complete command string including all
182 parameters and arguments.
183
184 Sample input:
185
186 robot_cmd_buf: robot -v
187 OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot no' -v
188 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
189 quiet:0 -v test_mode:0 -v debug:0
190 --outputdir='/home/user1/status/children/'
191 --output=dummy1.Auto_reboot.170802.124544.output.xml
192 --log=dummy1.Auto_reboot.170802.124544.log.html
193 --report=dummy1.Auto_reboot.170802.124544.report.html
194 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
195
196 Sample output:
197
198 robot_cmd_buf_dict:
199 robot_cmd_buf_dict[command]: robot
200 robot_cmd_buf_dict[v]:
201 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1
202 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto
203 Reboot no
204 robot_cmd_buf_dict[v][2]:
205 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
206 robot_cmd_buf_dict[v][3]: quiet:0
207 robot_cmd_buf_dict[v][4]: test_mode:0
208 robot_cmd_buf_dict[v][5]: debug:0
209 robot_cmd_buf_dict[outputdir]:
210 /home/user1/status/children/
211 robot_cmd_buf_dict[output]:
212 dummy1.Auto_reboot.170802.124544.output.xml
213 robot_cmd_buf_dict[log]:
214 dummy1.Auto_reboot.170802.124544.log.html
215 robot_cmd_buf_dict[report]:
216 dummy1.Auto_reboot.170802.124544.report.html
217 robot_cmd_buf_dict[positional]:
218 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
219 """
220
221 # We want the parms in the string broken down the way bash would do it,
222 # so we'll call upon bash to do that by creating a simple inline bash
223 # function.
224 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
225 " echo $parm ; done ; }"
226
227 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
228 quiet=1, print_output=0)
229 command_string_list = outbuf.rstrip("\n").split("\n")
230
231 command_string_dict = collections.OrderedDict()
232 ix = 1
233 command_string_dict['command'] = command_string_list[0]
234 while ix < len(command_string_list):
235 if command_string_list[ix].startswith("--"):
236 key, value = command_string_list[ix].split("=")
237 key = key.lstrip("-")
238 elif command_string_list[ix].startswith("-"):
239 key = command_string_list[ix].lstrip("-")
240 ix += 1
241 try:
242 value = command_string_list[ix]
243 except IndexError:
244 value = ""
245 else:
246 key = 'positional'
247 value = command_string_list[ix]
248 if key in command_string_dict:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500249 if isinstance(command_string_dict[key], str):
Michael Walshf41fac82017-08-02 15:05:24 -0500250 command_string_dict[key] = [command_string_dict[key]]
251 command_string_dict[key].append(value)
252 else:
253 command_string_dict[key] = value
254 ix += 1
255
256 return command_string_dict
Michael Walsh21083d22018-06-01 14:19:32 -0500257
258
259# Save the original SIGALRM handler for later restoration by shell_cmd.
260original_sigalrm_handler = signal.getsignal(signal.SIGALRM)
261
262
263def shell_cmd_timed_out(signal_number,
264 frame):
265 r"""
266 Handle an alarm signal generated during the shell_cmd function.
267 """
268
269 gp.dprint_executing()
270 # Get subprocess pid from shell_cmd's call stack.
271 sub_proc = gp.get_stack_var('sub_proc', 0)
272 pid = sub_proc.pid
273 # Terminate the child process.
274 os.kill(pid, signal.SIGTERM)
275 # Restore the original SIGALRM handler.
276 signal.signal(signal.SIGALRM, original_sigalrm_handler)
277
278 return
279
280
281def shell_cmd(command_string,
282 quiet=None,
Michael Walsh55abd1b2018-08-31 13:00:50 -0500283 print_output=None,
Michael Walsh21083d22018-06-01 14:19:32 -0500284 show_err=1,
285 test_mode=0,
286 time_out=None,
287 max_attempts=1,
288 retry_sleep_time=5,
289 allowed_shell_rcs=[0],
290 ignore_err=None,
Michael Walshfaafa9c2018-06-27 16:39:31 -0500291 return_stderr=0,
292 fork=0):
Michael Walsh21083d22018-06-01 14:19:32 -0500293 r"""
294 Run the given command string in a shell and return a tuple consisting of
295 the shell return code and the output.
296
297 Description of argument(s):
298 command_string The command string to be run in a shell
299 (e.g. "ls /tmp").
300 quiet If set to 0, this function will print
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500301 "Issuing: <cmd string>" to stdout. When
302 the quiet argument is set to None, this
303 function will assign a default value by
304 searching upward in the stack for the
305 quiet variable value. If no such value is
306 found, quiet is set to 0.
Michael Walsh21083d22018-06-01 14:19:32 -0500307 print_output If this is set, this function will print
308 the stdout/stderr generated by the shell
309 command to stdout.
310 show_err If show_err is set, this function will
311 print a standardized error report if the
312 shell command fails (i.e. if the shell
313 command returns a shell_rc that is not in
314 allowed_shell_rcs). Note: Error text is
315 only printed if ALL attempts to run the
316 command_string fail. In other words, if
317 the command execution is ultimately
318 successful, initial failures are hidden.
319 test_mode If test_mode is set, this function will
320 not actually run the command. If
321 print_output is also set, this function
322 will print "(test_mode) Issuing: <cmd
323 string>" to stdout. A caller should call
324 shell_cmd directly if they wish to have
325 the command string run unconditionally.
326 They should call the t_shell_cmd wrapper
327 (defined below) if they wish to run the
328 command string only if the prevailing
329 test_mode variable is set to 0.
330 time_out A time-out value expressed in seconds. If
331 the command string has not finished
332 executing within <time_out> seconds, it
333 will be halted and counted as an error.
334 max_attempts The max number of attempts that should be
335 made to run the command string.
336 retry_sleep_time The number of seconds to sleep between
337 attempts.
338 allowed_shell_rcs A list of integers indicating which
339 shell_rc values are not to be considered
340 errors.
341 ignore_err Ignore error means that a failure
342 encountered by running the command string
343 will not be raised as a python exception.
344 When the ignore_err argument is set to
345 None, this function will assign a default
346 value by searching upward in the stack for
347 the ignore_err variable value. If no such
348 value is found, ignore_err is set to 1.
349 return_stderr If return_stderr is set, this function
350 will process the stdout and stderr streams
351 from the shell command separately. In
352 such a case, the tuple returned by this
353 function will consist of three values
354 rather than just two: rc, stdout, stderr.
Michael Walshfaafa9c2018-06-27 16:39:31 -0500355 fork Run the command string asynchronously
356 (i.e. don't wait for status of the child
357 process and don't try to get
358 stdout/stderr).
Michael Walsh21083d22018-06-01 14:19:32 -0500359 """
360
361 # Assign default values to some of the arguments to this function.
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500362 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
Michael Walsh55abd1b2018-08-31 13:00:50 -0500363 print_output = int(gm.dft(print_output, not quiet))
Michael Walsh45fead42018-09-26 17:20:48 -0500364 show_err = int(show_err)
Michael Walsh22c3b6d2018-11-16 15:26:05 -0600365 global_ignore_err = gp.get_var_value(ignore_err, 1)
366 stack_ignore_err = gp.get_stack_var('ignore_err', global_ignore_err)
367 ignore_err = int(gm.dft(ignore_err, gm.dft(stack_ignore_err, 1)))
Michael Walsh21083d22018-06-01 14:19:32 -0500368
369 err_msg = gv.svalid_value(command_string)
370 if err_msg != "":
371 raise ValueError(err_msg)
372
373 if not quiet:
374 gp.print_issuing(command_string, test_mode)
375
376 if test_mode:
377 if return_stderr:
378 return 0, "", ""
379 else:
380 return 0, ""
381
382 # Convert each list entry to a signed value.
383 allowed_shell_rcs = [gm.to_signed(x) for x in allowed_shell_rcs]
384
385 if return_stderr:
386 stderr = subprocess.PIPE
387 else:
388 stderr = subprocess.STDOUT
389
390 shell_rc = 0
391 out_buf = ""
392 err_buf = ""
393 # Write all output to func_history_stdout rather than directly to stdout.
394 # This allows us to decide what to print after all attempts to run the
395 # command string have been made. func_history_stdout will contain the
396 # complete stdout history from the current invocation of this function.
397 func_history_stdout = ""
398 for attempt_num in range(1, max_attempts + 1):
399 sub_proc = subprocess.Popen(command_string,
400 bufsize=1,
401 shell=True,
402 executable='/bin/bash',
403 stdout=subprocess.PIPE,
404 stderr=stderr)
405 out_buf = ""
406 err_buf = ""
407 # Output from this loop iteration is written to func_stdout for later
408 # processing.
409 func_stdout = ""
Michael Walshfaafa9c2018-06-27 16:39:31 -0500410 if fork:
411 break
Michael Walsh21083d22018-06-01 14:19:32 -0500412 command_timed_out = False
413 if time_out is not None:
414 # Designate a SIGALRM handling function and set alarm.
415 signal.signal(signal.SIGALRM, shell_cmd_timed_out)
416 signal.alarm(time_out)
417 try:
418 if return_stderr:
419 for line in sub_proc.stderr:
George Keishing36efbc02018-12-12 10:18:23 -0600420 try:
421 err_buf += line
422 except TypeError:
423 line = line.decode("utf-8")
424 err_buf += line
Michael Walsh21083d22018-06-01 14:19:32 -0500425 if not print_output:
426 continue
427 func_stdout += line
428 for line in sub_proc.stdout:
George Keishing36efbc02018-12-12 10:18:23 -0600429 try:
430 out_buf += line
431 except TypeError:
432 line = line.decode("utf-8")
433 out_buf += line
Michael Walsh21083d22018-06-01 14:19:32 -0500434 if not print_output:
435 continue
436 func_stdout += line
437 except IOError:
438 command_timed_out = True
439 sub_proc.communicate()
440 shell_rc = sub_proc.returncode
441 # Restore the original SIGALRM handler and clear the alarm.
442 signal.signal(signal.SIGALRM, original_sigalrm_handler)
443 signal.alarm(0)
444 if shell_rc in allowed_shell_rcs:
445 break
446 err_msg = "The prior shell command failed.\n"
Michael Walsha750ed72018-07-24 16:18:09 -0500447 if quiet:
448 err_msg += gp.sprint_var(command_string)
Michael Walsh21083d22018-06-01 14:19:32 -0500449 if command_timed_out:
450 err_msg += gp.sprint_var(command_timed_out)
451 err_msg += gp.sprint_var(time_out)
452 err_msg += gp.sprint_varx("child_pid", sub_proc.pid)
453 err_msg += gp.sprint_var(attempt_num)
454 err_msg += gp.sprint_var(shell_rc, 1)
455 err_msg += gp.sprint_var(allowed_shell_rcs, 1)
456 if not print_output:
457 if return_stderr:
458 err_msg += "err_buf:\n" + err_buf
459 err_msg += "out_buf:\n" + out_buf
460 if show_err:
Michael Walshc108e422019-03-28 12:27:18 -0500461 func_stdout += gp.sprint_error_report(err_msg)
Michael Walsh21083d22018-06-01 14:19:32 -0500462 func_history_stdout += func_stdout
463 if attempt_num < max_attempts:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500464 func_history_stdout += gp.sprint_issuing("time.sleep("
465 + str(retry_sleep_time)
466 + ")")
Michael Walsh21083d22018-06-01 14:19:32 -0500467 time.sleep(retry_sleep_time)
468
469 if shell_rc not in allowed_shell_rcs:
470 func_stdout = func_history_stdout
471
Michael Walshc108e422019-03-28 12:27:18 -0500472 gp.gp_print(func_stdout)
Michael Walsh21083d22018-06-01 14:19:32 -0500473
474 if shell_rc not in allowed_shell_rcs:
475 if not ignore_err:
476 if robot_env:
477 BuiltIn().fail(err_msg)
478 else:
479 raise ValueError("The prior shell command failed.\n")
480
481 if return_stderr:
482 return shell_rc, out_buf, err_buf
483 else:
484 return shell_rc, out_buf
485
486
487def t_shell_cmd(command_string, **kwargs):
488 r"""
489 Search upward in the the call stack to obtain the test_mode argument, add
490 it to kwargs and then call shell_cmd and return the result.
491
492 See shell_cmd prolog for details on all arguments.
493 """
494
495 if 'test_mode' in kwargs:
496 error_message = "Programmer error - test_mode is not a valid" +\
497 " argument to this function."
498 gp.print_error_report(error_message)
499 exit(1)
500
501 test_mode = gp.get_stack_var('test_mode',
502 int(gp.get_var_value(None, 0, "test_mode")))
503 kwargs['test_mode'] = test_mode
504
505 return shell_cmd(command_string, **kwargs)
George Keishing16244c22019-01-31 16:16:14 +0000506
507
508def re_order_kwargs(stack_frame_ix, **kwargs):
509 r"""
510 Re-order the kwargs to match the order in which they were specified on a
511 function invocation and return as an ordered dictionary.
512
513 Note that this re_order_kwargs function should not be necessary in python
514 versions 3.6 and beyond.
515
516 Example:
517
518 The caller calls func1 like this:
519
520 func1('mike', arg1='one', arg2='two', arg3='three')
521
522 And func1 is defined as follows:
523
524 def func1(first_arg, **kwargs):
525
526 kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs)
527
528 The kwargs dictionary before calling re_order_kwargs (where order is not
529 guaranteed):
530
531 kwargs:
532 kwargs[arg3]: three
533 kwargs[arg2]: two
534 kwargs[arg1]: one
535
536 The kwargs dictionary after calling re_order_kwargs:
537
538 kwargs:
539 kwargs[arg1]: one
540 kwargs[arg2]: two
541 kwargs[arg3]: three
542
543 Note that the re-ordered kwargs match the order specified on the call to
544 func1.
545
546 Description of argument(s):
547 stack_frame_ix The stack frame of the function whose
548 kwargs values must be re-ordered. 0 is
549 the stack frame of re_order_kwargs, 1 is
550 the stack from of its caller and so on.
551 kwargs The keyword argument dictionary which is
552 to be re-ordered.
553 """
554
555 new_kwargs = collections.OrderedDict()
556
557 # Get position number of first keyword on the calling line of code.
558 (args, varargs, keywords, locals) =\
559 inspect.getargvalues(inspect.stack()[stack_frame_ix][0])
560 first_kwarg_pos = 1 + len(args)
561 if varargs is not None:
562 first_kwarg_pos += len(locals[varargs])
563 for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)):
564 # This will result in an arg_name value such as "arg1='one'".
565 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2)
566 # Continuing with the prior example, the following line will result
567 # in key being set to 'arg1'.
568 key = arg_name.split('=')[0]
569 new_kwargs[key] = kwargs[key]
570
571 return new_kwargs
572
573
574def default_arg_delim(arg_dashes):
575 r"""
576 Return the default argument delimiter value for the given arg_dashes value.
577
578 Note: this function is useful for functions that manipulate bash command
579 line arguments (e.g. --parm=1 or -parm 1).
580
581 Description of argument(s):
582 arg_dashes The argument dashes specifier (usually,
583 "-" or "--").
584 """
585
586 if arg_dashes == "--":
587 return "="
588
589 return " "
590
591
592def create_command_string(command, *pos_parms, **options):
593 r"""
594 Create and return a bash command string consisting of the given arguments
595 formatted as text.
596
597 The default formatting of options is as follows:
598
599 <single dash><option name><space delim><option value>
600
601 Example:
602
603 -parm value
604
605 The caller can change the kind of dashes/delimiters used by specifying
606 "arg_dashes" and/or "arg_delims" as options. These options are processed
607 specially by the create_command_string function and do NOT get inserted
608 into the resulting command string. All options following the
609 arg_dashes/arg_delims options will then use the specified values for
610 dashes/delims. In the special case of arg_dashes equal to "--", the
611 arg_delim will automatically be changed to "=". See examples below.
612
613 Quoting rules:
614
615 The create_command_string function will single quote option values as
616 needed to prevent bash expansion. If the caller wishes to defeat this
617 action, they may single or double quote the option value themselves. See
618 examples below.
619
620 pos_parms are NOT automatically quoted. The caller is advised to either
621 explicitly add quotes or to use the quote_bash_parm functions to quote any
622 pos_parms.
623
624 Examples:
625
626 command_string = create_command_string('cd', '~')
627
628 Result:
629 cd ~
630
631 Note that the pos_parm ("~") does NOT get quoted, as per the
632 aforementioned rules. If quotes are desired, they may be added explicitly
633 by the caller:
634
635 command_string = create_command_string('cd', '\'~\'')
636
637 Result:
638 cd '~'
639
640 command_string = create_command_string('grep', '\'^[^ ]*=\'',
641 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
642
643 Result:
644 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
645
646 In the preceding example, note the use of None to cause the "i" parm to be
647 treated as a flag (i.e. no argument value is generated). Also, note the
648 use of arg_dashes to change the type of dashes used on all subsequent
649 options. The following example is equivalent to the prior. Note that
650 quote_bash_parm is used instead of including the quotes explicitly.
651
652 command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='),
653 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
654
655 Result:
656 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
657
658 In the following example, note the automatic quoting of the password
659 option, as per the aforementioned rules.
660
661 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
662 m='1', arg_dashes='--', password='${my_pw}')
663
664 However, let's say that the caller wishes to have bash expand the password
665 value. To achieve this, the caller can use double quotes:
666
667 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
668 m='1', arg_dashes='--', password='"${my_pw}"')
669
670 Result:
671 my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile
672
673 command_string = create_command_string('ipmitool', 'power status',
674 I='lanplus', C='3', U='root', P='0penBmc', H='wsbmc010')
675
676 Result:
677 ipmitool -I lanplus -C 3 -U root -P 0penBmc -H wsbmc010 power status
678
679 By default create_command_string will take measures to preserve the order
680 of the callers options. In some cases, this effort may fail (as when
681 calling directly from a robot program). In this case, the caller can
682 accept the responsibility of keeping an ordered list of options by calling
683 this function with the last positional parm as some kind of dictionary
684 (preferably an OrderedDict) and avoiding the use of any actual option args.
685
686 Example:
687 kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)])
688 command_string = create_command_string('my program', 'pos_parm1', kwargs)
689
690 Result:
691
692 my program -pass 0 -fail 0 pos_parm1
693
694 Note to programmers who wish to write a wrapper to this function: To get
695 the options to be processed correctly, the wrapper function must include a
696 _stack_frame_ix_ keyword argument to allow this function to properly
697 re-order options:
698
699 def create_ipmi_ext_command_string(command, **kwargs):
700
701 return create_command_string('ipmitool', command, _stack_frame_ix_=2,
702 **kwargs)
703
704 Example call of wrapper function:
705
706 command_string = create_ipmi_ext_command_string('power status',
707 I='lanplus')
708
709 Description of argument(s):
710 command The command (e.g. "cat", "sort",
711 "ipmitool", etc.).
712 pos_parms The positional parms for the command (e.g.
713 PATTERN, FILENAME, etc.). These will be
714 placed at the end of the resulting command
715 string.
716 options The command options (e.g. "-m 1",
717 "--max-count=NUM", etc.). Note that if
718 the value of any option is None, then it
719 will be understood to be a flag (for which
720 no value is required).
721 """
722
723 arg_dashes = "-"
724 delim = default_arg_delim(arg_dashes)
725
726 command_string = command
727
Michael Walsh23ac9b42019-02-20 16:46:04 -0600728 if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]):
George Keishing16244c22019-01-31 16:16:14 +0000729 # Convert pos_parms from tuple to list.
730 pos_parms = list(pos_parms)
731 # Re-assign options to be the last pos_parm value (which is a
732 # dictionary).
733 options = pos_parms[-1]
734 # Now delete the last pos_parm.
735 del pos_parms[-1]
736 else:
737 # Either get stack_frame_ix from the caller via options or set it to
738 # the default value.
739 if '_stack_frame_ix_' in options:
740 stack_frame_ix = options['_stack_frame_ix_']
741 del options['_stack_frame_ix_']
742 else:
743 stack_frame_ix = 1
744 # Re-establish the original options order as specified on the
745 # original line of code. This function depends on correct order.
746 options = re_order_kwargs(stack_frame_ix, **options)
747 for key, value in options.items():
748 # Check for special values in options and process them.
749 if key == "arg_dashes":
750 arg_dashes = str(value)
751 delim = default_arg_delim(arg_dashes)
752 continue
753 if key == "arg_delim":
754 delim = str(value)
755 continue
756 # Format the options elements into the command string.
757 command_string += " " + arg_dashes + key
758 if value is not None:
759 command_string += delim
760 if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)):
761 # Already quoted.
762 command_string += str(value)
763 else:
764 command_string += gm.quote_bash_parm(str(value))
Michael Walsh23ac9b42019-02-20 16:46:04 -0600765 # Finally, append the pos_parms to the end of the command_string. Use
766 # filter to eliminate blank pos parms.
767 command_string = ' '.join([command_string] + list(filter(None, pos_parms)))
George Keishing16244c22019-01-31 16:16:14 +0000768
769 return command_string