blob: ae56bf5fbd848fb174be5cda31229b413cf65690 [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 Walsh1429e122019-05-20 10:06:18 -050019import func_args as fa
Michael Walshd6901502017-11-14 12:58:37 -060020
21robot_env = gp.robot_env
22
Michael Walshafc53a22017-04-12 15:52:28 -050023if robot_env:
Michael Walsha3e2f532018-01-10 13:43:42 -060024 from robot.libraries.BuiltIn import BuiltIn
Michael Walshc3b512e2017-02-20 15:59:01 -060025
26
Michael Walsh21083d22018-06-01 14:19:32 -050027# cmd_fnc and cmd_fnc_u should now be considered deprecated. shell_cmd and
28# t_shell_cmd should be used instead.
Michael Walshc3b512e2017-02-20 15:59:01 -060029def cmd_fnc(cmd_buf,
30 quiet=None,
31 test_mode=None,
Michael Walshafc53a22017-04-12 15:52:28 -050032 debug=0,
Michael Walshc3b512e2017-02-20 15:59:01 -060033 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -050034 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -060035 return_stderr=0,
36 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -060037 r"""
Michael Walshcfe9fed2017-09-12 17:13:10 -050038 Run the given command in a shell and return the shell return code and the
39 output.
Michael Walshc3b512e2017-02-20 15:59:01 -060040
41 Description of arguments:
42 cmd_buf The command string to be run in a shell.
43 quiet Indicates whether this function should run
Michael Walshcfe9fed2017-09-12 17:13:10 -050044 the print_issuing() function which prints
45 "Issuing: <cmd string>" to stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060046 test_mode If test_mode is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050047 not actually run the command. If
48 print_output is set, it will print
49 "(test_mode) Issuing: <cmd string>" to
50 stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060051 debug If debug is set, this function will print
52 extra debug info.
53 print_output If this is set, this function will print
Michael Walshcfe9fed2017-09-12 17:13:10 -050054 the stdout/stderr generated by the shell
55 command.
Michael Walshc3b512e2017-02-20 15:59:01 -060056 show_err If show_err is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050057 print a standardized error report if the
58 shell command returns non-zero.
59 return_stderr If return_stderr is set, this function
60 will process the stdout and stderr streams
61 from the shell command separately. It
62 will also return stderr in addition to the
63 return code and the stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060064 """
65
Michael Walshcfe9fed2017-09-12 17:13:10 -050066 # Determine default values.
Michael Walshc3b512e2017-02-20 15:59:01 -060067 quiet = int(gm.global_default(quiet, 0))
68 test_mode = int(gm.global_default(test_mode, 0))
Michael Walshc3b512e2017-02-20 15:59:01 -060069
70 if debug:
Michael Walshafc53a22017-04-12 15:52:28 -050071 gp.print_vars(cmd_buf, quiet, test_mode, debug)
Michael Walshc3b512e2017-02-20 15:59:01 -060072
Michael Walshec01a6f2019-08-01 12:43:20 -050073 err_msg = gv.valid_value(cmd_buf)
Michael Walshc3b512e2017-02-20 15:59:01 -060074 if err_msg != "":
75 raise ValueError(err_msg)
76
77 if not quiet:
Michael Walshafc53a22017-04-12 15:52:28 -050078 gp.pissuing(cmd_buf, test_mode)
Michael Walshc3b512e2017-02-20 15:59:01 -060079
80 if test_mode:
Michael Walshcfe9fed2017-09-12 17:13:10 -050081 if return_stderr:
82 return 0, "", ""
83 else:
84 return 0, ""
85
86 if return_stderr:
87 err_buf = ""
88 stderr = subprocess.PIPE
89 else:
90 stderr = subprocess.STDOUT
Michael Walshc3b512e2017-02-20 15:59:01 -060091
92 sub_proc = subprocess.Popen(cmd_buf,
93 bufsize=1,
94 shell=True,
Michael Walsh3ba8ecd2018-04-24 11:33:25 -050095 executable='/bin/bash',
Michael Walshc3b512e2017-02-20 15:59:01 -060096 stdout=subprocess.PIPE,
Michael Walshcfe9fed2017-09-12 17:13:10 -050097 stderr=stderr)
Michael Walshc3b512e2017-02-20 15:59:01 -060098 out_buf = ""
Michael Walshcfe9fed2017-09-12 17:13:10 -050099 if return_stderr:
100 for line in sub_proc.stderr:
George Keishing36efbc02018-12-12 10:18:23 -0600101 try:
102 err_buf += line
103 except TypeError:
104 line = line.decode("utf-8")
105 err_buf += line
Michael Walshcfe9fed2017-09-12 17:13:10 -0500106 if not print_output:
107 continue
Michael Walshc108e422019-03-28 12:27:18 -0500108 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600109 for line in sub_proc.stdout:
George Keishing36efbc02018-12-12 10:18:23 -0600110 try:
111 out_buf += line
112 except TypeError:
113 line = line.decode("utf-8")
114 out_buf += line
Michael Walshc3b512e2017-02-20 15:59:01 -0600115 if not print_output:
116 continue
Michael Walshc108e422019-03-28 12:27:18 -0500117 gp.gp_print(line)
Michael Walshc3b512e2017-02-20 15:59:01 -0600118 if print_output and not robot_env:
119 sys.stdout.flush()
120 sub_proc.communicate()
121 shell_rc = sub_proc.returncode
Michael Walsha3e2f532018-01-10 13:43:42 -0600122 if shell_rc != 0:
123 err_msg = "The prior shell command failed.\n"
Michael Walsh1429e122019-05-20 10:06:18 -0500124 err_msg += gp.sprint_var(shell_rc, gp.hexa())
Michael Walshcfe9fed2017-09-12 17:13:10 -0500125 if not print_output:
126 err_msg += "out_buf:\n" + out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600127
Michael Walsha3e2f532018-01-10 13:43:42 -0600128 if show_err:
Michael Walshc108e422019-03-28 12:27:18 -0500129 gp.print_error_report(err_msg)
Michael Walsha3e2f532018-01-10 13:43:42 -0600130 if not ignore_err:
131 if robot_env:
132 BuiltIn().fail(err_msg)
133 else:
134 raise ValueError(err_msg)
Michael Walshcfe9fed2017-09-12 17:13:10 -0500135
136 if return_stderr:
137 return shell_rc, out_buf, err_buf
138 else:
139 return shell_rc, out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600140
Michael Walshc3b512e2017-02-20 15:59:01 -0600141
Michael Walshc3b512e2017-02-20 15:59:01 -0600142def cmd_fnc_u(cmd_buf,
143 quiet=None,
144 debug=None,
145 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500146 show_err=1,
Michael Walsha3e2f532018-01-10 13:43:42 -0600147 return_stderr=0,
148 ignore_err=1):
Michael Walshc3b512e2017-02-20 15:59:01 -0600149 r"""
150 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details.
151
152 Note the "u" in "cmd_fnc_u" stands for "unconditional".
153 """
154
155 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500156 print_output=print_output, show_err=show_err,
Michael Walsha3e2f532018-01-10 13:43:42 -0600157 return_stderr=return_stderr, ignore_err=ignore_err)
Michael Walshc3b512e2017-02-20 15:59:01 -0600158
Michael Walshf41fac82017-08-02 15:05:24 -0500159
Michael Walshf41fac82017-08-02 15:05:24 -0500160def parse_command_string(command_string):
Michael Walshf41fac82017-08-02 15:05:24 -0500161 r"""
162 Parse a bash command-line command string and return the result as a
163 dictionary of parms.
164
165 This can be useful for answering questions like "What did the user specify
166 as the value for parm x in the command string?".
167
168 This function expects the command string to follow the following posix
169 conventions:
170 - Short parameters:
171 -<parm name><space><arg value>
172 - Long parameters:
173 --<parm name>=<arg value>
174
175 The first item in the string will be considered to be the command. All
176 values not conforming to the specifications above will be considered
177 positional parms. If there are multiple parms with the same name, they
178 will be put into a list (see illustration below where "-v" is specified
179 multiple times).
180
181 Description of argument(s):
182 command_string The complete command string including all
183 parameters and arguments.
184
185 Sample input:
186
187 robot_cmd_buf: robot -v
188 OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot no' -v
189 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
190 quiet:0 -v test_mode:0 -v debug:0
191 --outputdir='/home/user1/status/children/'
192 --output=dummy1.Auto_reboot.170802.124544.output.xml
193 --log=dummy1.Auto_reboot.170802.124544.log.html
194 --report=dummy1.Auto_reboot.170802.124544.report.html
195 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
196
197 Sample output:
198
199 robot_cmd_buf_dict:
200 robot_cmd_buf_dict[command]: robot
201 robot_cmd_buf_dict[v]:
202 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1
203 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto
204 Reboot no
205 robot_cmd_buf_dict[v][2]:
206 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
207 robot_cmd_buf_dict[v][3]: quiet:0
208 robot_cmd_buf_dict[v][4]: test_mode:0
209 robot_cmd_buf_dict[v][5]: debug:0
210 robot_cmd_buf_dict[outputdir]:
211 /home/user1/status/children/
212 robot_cmd_buf_dict[output]:
213 dummy1.Auto_reboot.170802.124544.output.xml
214 robot_cmd_buf_dict[log]:
215 dummy1.Auto_reboot.170802.124544.log.html
216 robot_cmd_buf_dict[report]:
217 dummy1.Auto_reboot.170802.124544.report.html
218 robot_cmd_buf_dict[positional]:
219 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
220 """
221
222 # We want the parms in the string broken down the way bash would do it,
223 # so we'll call upon bash to do that by creating a simple inline bash
224 # function.
225 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
226 " echo $parm ; done ; }"
227
228 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
229 quiet=1, print_output=0)
230 command_string_list = outbuf.rstrip("\n").split("\n")
231
232 command_string_dict = collections.OrderedDict()
233 ix = 1
234 command_string_dict['command'] = command_string_list[0]
235 while ix < len(command_string_list):
236 if command_string_list[ix].startswith("--"):
237 key, value = command_string_list[ix].split("=")
238 key = key.lstrip("-")
239 elif command_string_list[ix].startswith("-"):
240 key = command_string_list[ix].lstrip("-")
241 ix += 1
242 try:
243 value = command_string_list[ix]
244 except IndexError:
245 value = ""
246 else:
247 key = 'positional'
248 value = command_string_list[ix]
249 if key in command_string_dict:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500250 if isinstance(command_string_dict[key], str):
Michael Walshf41fac82017-08-02 15:05:24 -0500251 command_string_dict[key] = [command_string_dict[key]]
252 command_string_dict[key].append(value)
253 else:
254 command_string_dict[key] = value
255 ix += 1
256
257 return command_string_dict
Michael Walsh21083d22018-06-01 14:19:32 -0500258
259
260# Save the original SIGALRM handler for later restoration by shell_cmd.
261original_sigalrm_handler = signal.getsignal(signal.SIGALRM)
262
263
264def shell_cmd_timed_out(signal_number,
265 frame):
266 r"""
267 Handle an alarm signal generated during the shell_cmd function.
268 """
269
270 gp.dprint_executing()
Michael Walsh3fb26182019-08-28 16:51:05 -0500271 global command_timed_out
272 command_timed_out = True
Michael Walsh21083d22018-06-01 14:19:32 -0500273 # Get subprocess pid from shell_cmd's call stack.
274 sub_proc = gp.get_stack_var('sub_proc', 0)
275 pid = sub_proc.pid
Michael Walsh3fb26182019-08-28 16:51:05 -0500276 gp.dprint_var(pid)
277 # Terminate the child process group.
278 os.killpg(pid, signal.SIGKILL)
Michael Walsh21083d22018-06-01 14:19:32 -0500279 # Restore the original SIGALRM handler.
280 signal.signal(signal.SIGALRM, original_sigalrm_handler)
281
282 return
283
284
285def shell_cmd(command_string,
286 quiet=None,
Michael Walsh55abd1b2018-08-31 13:00:50 -0500287 print_output=None,
Michael Walsh21083d22018-06-01 14:19:32 -0500288 show_err=1,
289 test_mode=0,
290 time_out=None,
291 max_attempts=1,
292 retry_sleep_time=5,
Michael Walsh3fb26182019-08-28 16:51:05 -0500293 valid_rcs=[0],
Michael Walsh21083d22018-06-01 14:19:32 -0500294 ignore_err=None,
Michael Walshfaafa9c2018-06-27 16:39:31 -0500295 return_stderr=0,
296 fork=0):
Michael Walsh21083d22018-06-01 14:19:32 -0500297 r"""
298 Run the given command string in a shell and return a tuple consisting of
299 the shell return code and the output.
300
301 Description of argument(s):
302 command_string The command string to be run in a shell
303 (e.g. "ls /tmp").
304 quiet If set to 0, this function will print
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500305 "Issuing: <cmd string>" to stdout. When
306 the quiet argument is set to None, this
307 function will assign a default value by
308 searching upward in the stack for the
309 quiet variable value. If no such value is
310 found, quiet is set to 0.
Michael Walsh21083d22018-06-01 14:19:32 -0500311 print_output If this is set, this function will print
312 the stdout/stderr generated by the shell
313 command to stdout.
314 show_err If show_err is set, this function will
315 print a standardized error report if the
316 shell command fails (i.e. if the shell
317 command returns a shell_rc that is not in
Michael Walsh3fb26182019-08-28 16:51:05 -0500318 valid_rcs). Note: Error text is only
319 printed if ALL attempts to run the
Michael Walsh21083d22018-06-01 14:19:32 -0500320 command_string fail. In other words, if
321 the command execution is ultimately
322 successful, initial failures are hidden.
323 test_mode If test_mode is set, this function will
324 not actually run the command. If
325 print_output is also set, this function
326 will print "(test_mode) Issuing: <cmd
327 string>" to stdout. A caller should call
328 shell_cmd directly if they wish to have
329 the command string run unconditionally.
330 They should call the t_shell_cmd wrapper
331 (defined below) if they wish to run the
332 command string only if the prevailing
333 test_mode variable is set to 0.
334 time_out A time-out value expressed in seconds. If
335 the command string has not finished
336 executing within <time_out> seconds, it
337 will be halted and counted as an error.
338 max_attempts The max number of attempts that should be
339 made to run the command string.
340 retry_sleep_time The number of seconds to sleep between
341 attempts.
Michael Walsh3fb26182019-08-28 16:51:05 -0500342 valid_rcs A list of integers indicating which
Michael Walsh21083d22018-06-01 14:19:32 -0500343 shell_rc values are not to be considered
344 errors.
345 ignore_err Ignore error means that a failure
346 encountered by running the command string
347 will not be raised as a python exception.
348 When the ignore_err argument is set to
349 None, this function will assign a default
350 value by searching upward in the stack for
351 the ignore_err variable value. If no such
352 value is found, ignore_err is set to 1.
353 return_stderr If return_stderr is set, this function
354 will process the stdout and stderr streams
355 from the shell command separately. In
356 such a case, the tuple returned by this
357 function will consist of three values
358 rather than just two: rc, stdout, stderr.
Michael Walshfaafa9c2018-06-27 16:39:31 -0500359 fork Run the command string asynchronously
360 (i.e. don't wait for status of the child
361 process and don't try to get
362 stdout/stderr).
Michael Walsh21083d22018-06-01 14:19:32 -0500363 """
364
Michael Walsh3fb26182019-08-28 16:51:05 -0500365 err_msg = gv.valid_value(command_string)
366 if err_msg:
367 raise ValueError(err_msg)
368
Michael Walsh21083d22018-06-01 14:19:32 -0500369 # Assign default values to some of the arguments to this function.
Michael Walsh4cb9b2a2018-06-08 17:57:00 -0500370 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
Michael Walsh55abd1b2018-08-31 13:00:50 -0500371 print_output = int(gm.dft(print_output, not quiet))
Michael Walsh45fead42018-09-26 17:20:48 -0500372 show_err = int(show_err)
Michael Walsh3fb26182019-08-28 16:51:05 -0500373 ignore_err = int(gm.dft(ignore_err, gp.get_stack_var('ignore_err', 1)))
Michael Walsh21083d22018-06-01 14:19:32 -0500374
Michael Walsh3fb26182019-08-28 16:51:05 -0500375 gp.qprint_issuing(command_string, test_mode)
Michael Walsh21083d22018-06-01 14:19:32 -0500376 if test_mode:
Michael Walsh3fb26182019-08-28 16:51:05 -0500377 return (0, "", "") if return_stderr else (0, "")
Michael Walsh21083d22018-06-01 14:19:32 -0500378
Michael Walsh3fb26182019-08-28 16:51:05 -0500379 # Convert a string python dictionary definition to a dictionary.
380 valid_rcs = fa.source_to_object(valid_rcs)
Michael Walsh21083d22018-06-01 14:19:32 -0500381 # Convert each list entry to a signed value.
Michael Walsh3fb26182019-08-28 16:51:05 -0500382 valid_rcs = [gm.to_signed(x) for x in valid_rcs]
Michael Walsh21083d22018-06-01 14:19:32 -0500383
Michael Walsh3fb26182019-08-28 16:51:05 -0500384 stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT
Michael Walsh21083d22018-06-01 14:19:32 -0500385
Michael Walsh3fb26182019-08-28 16:51:05 -0500386 # Write all output to func_out_history_buf rather than directly to
387 # stdout. This allows us to decide what to print after all attempts to
388 # run the command string have been made. func_out_history_buf will
389 # contain the complete history from the current invocation of this
390 # function.
391 global command_timed_out
392 command_timed_out = False
393 func_out_history_buf = ""
Michael Walsh21083d22018-06-01 14:19:32 -0500394 for attempt_num in range(1, max_attempts + 1):
395 sub_proc = subprocess.Popen(command_string,
Michael Walsh3fb26182019-08-28 16:51:05 -0500396 preexec_fn=os.setsid,
Michael Walsh21083d22018-06-01 14:19:32 -0500397 bufsize=1,
398 shell=True,
Michael Walsh3fb26182019-08-28 16:51:05 -0500399 universal_newlines=True,
Michael Walsh21083d22018-06-01 14:19:32 -0500400 executable='/bin/bash',
401 stdout=subprocess.PIPE,
402 stderr=stderr)
Michael Walshfaafa9c2018-06-27 16:39:31 -0500403 if fork:
Michael Walsh3fb26182019-08-28 16:51:05 -0500404 return (0, "", "") if return_stderr else (0, "")
405
406 if time_out:
407 command_timed_out = False
Michael Walsh21083d22018-06-01 14:19:32 -0500408 # Designate a SIGALRM handling function and set alarm.
409 signal.signal(signal.SIGALRM, shell_cmd_timed_out)
410 signal.alarm(time_out)
411 try:
Michael Walsh3fb26182019-08-28 16:51:05 -0500412 stdout_buf, stderr_buf = sub_proc.communicate()
Michael Walsh21083d22018-06-01 14:19:32 -0500413 except IOError:
414 command_timed_out = True
Michael Walsh21083d22018-06-01 14:19:32 -0500415 # Restore the original SIGALRM handler and clear the alarm.
416 signal.signal(signal.SIGALRM, original_sigalrm_handler)
417 signal.alarm(0)
Michael Walsh3fb26182019-08-28 16:51:05 -0500418
419 # Output from this loop iteration is written to func_out_buf for
420 # later processing. This can include stdout, stderr and our own error
421 # messages.
422 func_out_buf = ""
423 if print_output:
424 if return_stderr:
425 func_out_buf += stderr_buf
426 func_out_buf += stdout_buf
427 shell_rc = sub_proc.returncode
428 if shell_rc in valid_rcs:
Michael Walsh21083d22018-06-01 14:19:32 -0500429 break
430 err_msg = "The prior shell command failed.\n"
Michael Walsh21083d22018-06-01 14:19:32 -0500431 err_msg += gp.sprint_var(attempt_num)
Michael Walsh3fb26182019-08-28 16:51:05 -0500432 err_msg += gp.sprint_vars(command_string, command_timed_out, time_out)
433 err_msg += gp.sprint_varx("child_pid", sub_proc.pid)
434 err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa())
Michael Walsh21083d22018-06-01 14:19:32 -0500435 if not print_output:
436 if return_stderr:
Michael Walsh3fb26182019-08-28 16:51:05 -0500437 err_msg += "stderr_buf:\n" + stderr_buf
438 err_msg += "stdout_buf:\n" + stdout_buf
Michael Walsh21083d22018-06-01 14:19:32 -0500439 if show_err:
Michael Walsh3fb26182019-08-28 16:51:05 -0500440 func_out_buf += gp.sprint_error_report(err_msg)
Michael Walsh21083d22018-06-01 14:19:32 -0500441 if attempt_num < max_attempts:
Michael Walsh3fb26182019-08-28 16:51:05 -0500442 cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")"
443 if show_err:
444 func_out_buf += gp.sprint_issuing(cmd_buf)
445 exec(cmd_buf)
446 func_out_history_buf += func_out_buf
Michael Walsh21083d22018-06-01 14:19:32 -0500447
Michael Walsh3fb26182019-08-28 16:51:05 -0500448 if shell_rc in valid_rcs:
449 gp.gp_print(func_out_buf)
450 else:
451 if show_err:
452 gp.gp_print(func_out_history_buf, stream='stderr')
453 else:
454 # There is no error information to show so just print output from
455 # last loop iteration.
456 gp.gp_print(func_out_buf)
Michael Walsh21083d22018-06-01 14:19:32 -0500457 if not ignore_err:
Michael Walsh3fb26182019-08-28 16:51:05 -0500458 # If the caller has already asked to show error info, avoid
459 # repeating that in the failure message.
460 err_msg = "The prior shell command failed.\n" if show_err \
461 else err_msg
Michael Walsh21083d22018-06-01 14:19:32 -0500462 if robot_env:
463 BuiltIn().fail(err_msg)
464 else:
Michael Walsh3fb26182019-08-28 16:51:05 -0500465 raise ValueError(err_msg)
Michael Walsh21083d22018-06-01 14:19:32 -0500466
Michael Walsh3fb26182019-08-28 16:51:05 -0500467 return (shell_rc, stdout_buf, stderr_buf) if return_stderr \
468 else (shell_rc, stdout_buf)
Michael Walsh21083d22018-06-01 14:19:32 -0500469
470
471def t_shell_cmd(command_string, **kwargs):
472 r"""
473 Search upward in the the call stack to obtain the test_mode argument, add
474 it to kwargs and then call shell_cmd and return the result.
475
476 See shell_cmd prolog for details on all arguments.
477 """
478
479 if 'test_mode' in kwargs:
480 error_message = "Programmer error - test_mode is not a valid" +\
481 " argument to this function."
482 gp.print_error_report(error_message)
483 exit(1)
484
Michael Walsh3fb26182019-08-28 16:51:05 -0500485 test_mode = int(gp.get_stack_var('test_mode', 0))
Michael Walsh21083d22018-06-01 14:19:32 -0500486 kwargs['test_mode'] = test_mode
487
488 return shell_cmd(command_string, **kwargs)
George Keishing16244c22019-01-31 16:16:14 +0000489
490
491def re_order_kwargs(stack_frame_ix, **kwargs):
492 r"""
493 Re-order the kwargs to match the order in which they were specified on a
494 function invocation and return as an ordered dictionary.
495
496 Note that this re_order_kwargs function should not be necessary in python
497 versions 3.6 and beyond.
498
499 Example:
500
501 The caller calls func1 like this:
502
503 func1('mike', arg1='one', arg2='two', arg3='three')
504
505 And func1 is defined as follows:
506
507 def func1(first_arg, **kwargs):
508
509 kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs)
510
511 The kwargs dictionary before calling re_order_kwargs (where order is not
512 guaranteed):
513
514 kwargs:
515 kwargs[arg3]: three
516 kwargs[arg2]: two
517 kwargs[arg1]: one
518
519 The kwargs dictionary after calling re_order_kwargs:
520
521 kwargs:
522 kwargs[arg1]: one
523 kwargs[arg2]: two
524 kwargs[arg3]: three
525
526 Note that the re-ordered kwargs match the order specified on the call to
527 func1.
528
529 Description of argument(s):
530 stack_frame_ix The stack frame of the function whose
531 kwargs values must be re-ordered. 0 is
532 the stack frame of re_order_kwargs, 1 is
533 the stack from of its caller and so on.
534 kwargs The keyword argument dictionary which is
535 to be re-ordered.
536 """
537
538 new_kwargs = collections.OrderedDict()
539
540 # Get position number of first keyword on the calling line of code.
541 (args, varargs, keywords, locals) =\
542 inspect.getargvalues(inspect.stack()[stack_frame_ix][0])
543 first_kwarg_pos = 1 + len(args)
544 if varargs is not None:
545 first_kwarg_pos += len(locals[varargs])
546 for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)):
547 # This will result in an arg_name value such as "arg1='one'".
548 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2)
549 # Continuing with the prior example, the following line will result
550 # in key being set to 'arg1'.
551 key = arg_name.split('=')[0]
552 new_kwargs[key] = kwargs[key]
553
554 return new_kwargs
555
556
557def default_arg_delim(arg_dashes):
558 r"""
559 Return the default argument delimiter value for the given arg_dashes value.
560
561 Note: this function is useful for functions that manipulate bash command
562 line arguments (e.g. --parm=1 or -parm 1).
563
564 Description of argument(s):
565 arg_dashes The argument dashes specifier (usually,
566 "-" or "--").
567 """
568
569 if arg_dashes == "--":
570 return "="
571
572 return " "
573
574
575def create_command_string(command, *pos_parms, **options):
576 r"""
577 Create and return a bash command string consisting of the given arguments
578 formatted as text.
579
580 The default formatting of options is as follows:
581
582 <single dash><option name><space delim><option value>
583
584 Example:
585
586 -parm value
587
588 The caller can change the kind of dashes/delimiters used by specifying
589 "arg_dashes" and/or "arg_delims" as options. These options are processed
590 specially by the create_command_string function and do NOT get inserted
591 into the resulting command string. All options following the
592 arg_dashes/arg_delims options will then use the specified values for
593 dashes/delims. In the special case of arg_dashes equal to "--", the
594 arg_delim will automatically be changed to "=". See examples below.
595
596 Quoting rules:
597
598 The create_command_string function will single quote option values as
599 needed to prevent bash expansion. If the caller wishes to defeat this
600 action, they may single or double quote the option value themselves. See
601 examples below.
602
603 pos_parms are NOT automatically quoted. The caller is advised to either
604 explicitly add quotes or to use the quote_bash_parm functions to quote any
605 pos_parms.
606
607 Examples:
608
609 command_string = create_command_string('cd', '~')
610
611 Result:
612 cd ~
613
614 Note that the pos_parm ("~") does NOT get quoted, as per the
615 aforementioned rules. If quotes are desired, they may be added explicitly
616 by the caller:
617
618 command_string = create_command_string('cd', '\'~\'')
619
620 Result:
621 cd '~'
622
623 command_string = create_command_string('grep', '\'^[^ ]*=\'',
624 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
625
626 Result:
627 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
628
629 In the preceding example, note the use of None to cause the "i" parm to be
630 treated as a flag (i.e. no argument value is generated). Also, note the
631 use of arg_dashes to change the type of dashes used on all subsequent
632 options. The following example is equivalent to the prior. Note that
633 quote_bash_parm is used instead of including the quotes explicitly.
634
635 command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='),
636 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
637
638 Result:
639 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
640
641 In the following example, note the automatic quoting of the password
642 option, as per the aforementioned rules.
643
644 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
645 m='1', arg_dashes='--', password='${my_pw}')
646
647 However, let's say that the caller wishes to have bash expand the password
648 value. To achieve this, the caller can use double quotes:
649
650 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
651 m='1', arg_dashes='--', password='"${my_pw}"')
652
653 Result:
654 my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile
655
656 command_string = create_command_string('ipmitool', 'power status',
657 I='lanplus', C='3', U='root', P='0penBmc', H='wsbmc010')
658
659 Result:
660 ipmitool -I lanplus -C 3 -U root -P 0penBmc -H wsbmc010 power status
661
662 By default create_command_string will take measures to preserve the order
663 of the callers options. In some cases, this effort may fail (as when
664 calling directly from a robot program). In this case, the caller can
665 accept the responsibility of keeping an ordered list of options by calling
666 this function with the last positional parm as some kind of dictionary
667 (preferably an OrderedDict) and avoiding the use of any actual option args.
668
669 Example:
670 kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)])
671 command_string = create_command_string('my program', 'pos_parm1', kwargs)
672
673 Result:
674
675 my program -pass 0 -fail 0 pos_parm1
676
Michael Walshfffddca2019-08-20 17:19:13 +0000677 Note to programmers who wish to write a wrapper to this function: If the
678 python version is less than 3.6, to get the options to be processed
679 correctly, the wrapper function must include a _stack_frame_ix_ keyword
680 argument to allow this function to properly re-order options:
George Keishing16244c22019-01-31 16:16:14 +0000681
682 def create_ipmi_ext_command_string(command, **kwargs):
683
684 return create_command_string('ipmitool', command, _stack_frame_ix_=2,
685 **kwargs)
686
687 Example call of wrapper function:
688
689 command_string = create_ipmi_ext_command_string('power status',
690 I='lanplus')
691
692 Description of argument(s):
693 command The command (e.g. "cat", "sort",
694 "ipmitool", etc.).
695 pos_parms The positional parms for the command (e.g.
696 PATTERN, FILENAME, etc.). These will be
697 placed at the end of the resulting command
698 string.
699 options The command options (e.g. "-m 1",
700 "--max-count=NUM", etc.). Note that if
701 the value of any option is None, then it
702 will be understood to be a flag (for which
703 no value is required).
704 """
705
706 arg_dashes = "-"
707 delim = default_arg_delim(arg_dashes)
708
709 command_string = command
710
Michael Walsh23ac9b42019-02-20 16:46:04 -0600711 if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]):
George Keishing16244c22019-01-31 16:16:14 +0000712 # Convert pos_parms from tuple to list.
713 pos_parms = list(pos_parms)
714 # Re-assign options to be the last pos_parm value (which is a
715 # dictionary).
716 options = pos_parms[-1]
717 # Now delete the last pos_parm.
718 del pos_parms[-1]
719 else:
720 # Either get stack_frame_ix from the caller via options or set it to
721 # the default value.
Michael Walshfffddca2019-08-20 17:19:13 +0000722 stack_frame_ix = options.pop('_stack_frame_ix_', 1)
723 if gm.python_version < gm.ordered_dict_version:
724 # Re-establish the original options order as specified on the
725 # original line of code. This function depends on correct order.
726 options = re_order_kwargs(stack_frame_ix, **options)
George Keishing16244c22019-01-31 16:16:14 +0000727 for key, value in options.items():
728 # Check for special values in options and process them.
729 if key == "arg_dashes":
730 arg_dashes = str(value)
731 delim = default_arg_delim(arg_dashes)
732 continue
733 if key == "arg_delim":
734 delim = str(value)
735 continue
736 # Format the options elements into the command string.
737 command_string += " " + arg_dashes + key
738 if value is not None:
739 command_string += delim
740 if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)):
741 # Already quoted.
742 command_string += str(value)
743 else:
744 command_string += gm.quote_bash_parm(str(value))
Michael Walsh23ac9b42019-02-20 16:46:04 -0600745 # Finally, append the pos_parms to the end of the command_string. Use
746 # filter to eliminate blank pos parms.
747 command_string = ' '.join([command_string] + list(filter(None, pos_parms)))
George Keishing16244c22019-01-31 16:16:14 +0000748
749 return command_string