blob: 5147b9d75dcf87e7523cede41689ae31d1d89d12 [file] [log] [blame]
Michael Walsh888be1f2019-03-26 12:02:36 -05001#!/usr/bin/env python
2
3r"""
4See help text for details.
5"""
6
7import sys
8import subprocess
Michael Walsh64fbc9c2019-04-05 16:20:26 -05009import re
Michael Walsh888be1f2019-03-26 12:02:36 -050010
11save_path_0 = sys.path[0]
12del sys.path[0]
13
14from gen_arg import *
15from gen_print import *
16from gen_valid import *
17from gen_misc import *
18from gen_cmd import *
19from var_funcs import *
20
21# Restore sys.path[0].
22sys.path.insert(0, save_path_0)
23
24# Set exit_on_error for gen_valid functions.
25set_exit_on_error(True)
26
27parser = argparse.ArgumentParser(
28 usage='%(prog)s [OPTIONS]',
29 description="%(prog)s will create a status file path name adhering to the"
30 + " following pattern: <status dir path>/<prefix>.yymmdd."
31 + "hhmmss.status. It will then run the command string and"
32 + " direct its stdout/stderr to the status file and optionally"
33 + " to stdout. This dual output streaming will be"
34 + " accomplished using either the \"script\" or the \"tee\""
35 + " program. %(prog)s will also set and export environment"
36 + " variable \"AUTO_STATUS_FILE_PATH\" for the benefit of"
37 + " child programs.",
38 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
39 prefix_chars='-+')
40
41parser.add_argument(
42 '--status_dir_path',
43 default='',
44 help="The path to the directory where the status file will be created."
45 + "%(default)s The default value is obtained from environment"
46 + " variable \"${STATUS_DIR_PATH}\", if set or from \"${HOME}/"
47 + "status/\".")
48
49parser.add_argument(
50 '--prefix',
51 default='',
52 help="The prefix for the generated file name.%(default)s The default value"
53 + " is the command portion (i.e. the first token) of the command"
54 + " string.")
55
56parser.add_argument(
57 '--status_file_name',
58 default='',
59 help="This allows the user to explicitly specify the status file name. If"
60 + " this argument is not used, %(prog)s composes a status file name."
61 + " If this argument is specified, the \"--prefix\" argument is"
62 + " ignored.")
63
64parser.add_argument(
65 '--stdout',
66 default=1,
67 type=int,
68 choices=[1, 0],
69 help="Indicates that stdout/stderr from the command string execution"
70 + " should be written to stdout as well as to the status file.")
71
72parser.add_argument(
73 '--tee',
74 default=0,
75 type=int,
76 choices=[1, 0],
77 help="Indicates that \"tee\" rather than \"script\" should be used.")
78
79parser.add_argument(
80 '--show_url',
81 default=0,
82 type=int,
83 choices=[1, 0],
84 help="Indicates that the status file path shown should be shown in the"
85 + " form of a url. If the output is to be viewed from a browser,"
86 + " this may well become a clickable link. Note that the"
87 + " get_file_path_url.py program must be found in the \"PATH\""
88 + " environment variable for this argument to be effective.")
89
90parser.add_argument(
91 'command_string',
92 default='',
93 nargs='*',
94 help="The command string to be run.%(default)s")
95
96# Populate stock_list with options we want.
97stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
98
99
100def exit_function(signal_number=0,
101 frame=None):
102 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500103 Execute whenever the program ends normally or with the signals that we catch (i.e. TERM, INT).
Michael Walsh888be1f2019-03-26 12:02:36 -0500104 """
105
106 dprint_executing()
107 dprint_var(signal_number)
108
109 qprint_pgm_footer()
110
111
112def signal_handler(signal_number,
113 frame):
114 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500115 Handle signals. Without a function to catch a SIGTERM or SIGINT, our program would terminate immediately
116 with return code 143 and without calling our exit_function.
Michael Walsh888be1f2019-03-26 12:02:36 -0500117 """
118
Michael Walsh410b1782019-10-22 15:56:18 -0500119 # Our convention is to set up exit_function with atexit.register() so there is no need to explicitly
120 # call exit_function from here.
Michael Walsh888be1f2019-03-26 12:02:36 -0500121
122 dprint_executing()
123
Michael Walsh410b1782019-10-22 15:56:18 -0500124 # Calling exit prevents us from returning to the code that was running when we received the signal.
Michael Walsh888be1f2019-03-26 12:02:36 -0500125 exit(0)
126
127
128def validate_parms():
129 r"""
130 Validate program parameters, etc.
131 """
132
133 global status_dir_path
134 global command_string
135
136 # Convert command_string from list to string.
137 command_string = " ".join(command_string)
138 set_pgm_arg(command_string)
139 valid_value(command_string)
140
141 if status_dir_path == "":
142 status_dir_path = \
143 os.environ.get("STATUS_DIR_PATH",
144 os.environ.get("HOME") + "/status/")
145 status_dir_path = add_trailing_slash(status_dir_path)
146 set_pgm_arg(status_dir_path)
147 valid_dir_path(status_dir_path)
148
149 global prefix
150 global status_file_name
151 if status_file_name == "":
152 if prefix == "":
153 prefix = command_string.split(" ")[0]
Michael Walsh410b1782019-10-22 15:56:18 -0500154 # File extensions (e.g. ".sh", ".py", .etc), look clumsy in status file names.
Michael Walsh64fbc9c2019-04-05 16:20:26 -0500155 extension_regex = "\\.[a-zA-Z0-9]{1,3}$"
156 prefix = re.sub(extension_regex, "", prefix)
Michael Walsh888be1f2019-03-26 12:02:36 -0500157 set_pgm_arg(prefix)
158 status_file_name = prefix + "." + file_date_time_stamp() + ".status"
159 set_pgm_arg(status_file_name)
160
161 global status_file_path
162
163 status_file_path = status_dir_path + status_file_name
164 # Set environment variable for the benefit of child programs.
165 os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path
166 # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value.
167 os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path
168
169 gen_post_validation(exit_function, signal_handler)
170
171
172def script_func(command_string, status_file_path):
173 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500174 Run the command string producing both stdout and file output via the script command and return the
175 shell_rc.
Michael Walsh888be1f2019-03-26 12:02:36 -0500176
177 Description of argument(s):
178 command_string The command string to be run.
Michael Walsh410b1782019-10-22 15:56:18 -0500179 status_file_path The path to the status file which is to contain a copy of all stdout.
Michael Walsh888be1f2019-03-26 12:02:36 -0500180 """
181
182 cmd_buf = "script -a -q -f " + status_file_path + " -c '" \
183 + escape_bash_quotes(command_string) + " ; printf \"\\n" \
184 + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'"
185 qprint_issuing(cmd_buf)
186 sub_proc = subprocess.Popen(cmd_buf, shell=True)
187 sub_proc.communicate()
188 shell_rc = sub_proc.returncode
189
Michael Walsh410b1782019-10-22 15:56:18 -0500190 # Retrieve return code by examining ret_code_str output statement from status file.
Michael Walsh888be1f2019-03-26 12:02:36 -0500191 # Example text to be analyzed.
192 # auto_status_file_ret_code: 127
193 cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \
194 + ret_code_str + ":[ ]+\""
195 rc, output = shell_cmd(cmd_buf)
196 key, value = parse_key_value(output)
197 shell_rc = int(value)
198
199 return shell_rc
200
201
202def tee_func(command_string, status_file_path):
203 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500204 Run the command string producing both stdout and file output via the tee command and return the shell_rc.
Michael Walsh888be1f2019-03-26 12:02:36 -0500205
206 Description of argument(s):
207 command_string The command string to be run.
Michael Walsh410b1782019-10-22 15:56:18 -0500208 status_file_path The path to the status file which is to contain a copy of all stdout.
Michael Walsh888be1f2019-03-26 12:02:36 -0500209 """
210
211 cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \
212 + status_file_path
213 qprint_issuing(cmd_buf)
214 sub_proc = subprocess.Popen(cmd_buf, shell=True)
215 sub_proc.communicate()
216 shell_rc = sub_proc.returncode
217
218 print
219 print_varx(ret_code_str, shell_rc)
220 with open(status_file_path, "a") as status_file:
221 # Append ret code string and status_file_path to end of status file.
222 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
223
224 return shell_rc
225
226
227def main():
228
229 gen_get_options(parser, stock_list)
230
231 validate_parms()
232
233 qprint_pgm_header()
234
235 global ret_code_str
236 ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code"
237
238 global show_url
239 if show_url:
240 shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0)
241 if shell_rc != 0:
242 show_url = 0
243 set_pgm_arg(show_url)
244 else:
245 shell_rc, status_file_url = shell_cmd("get_file_path_url.py "
246 + status_file_path)
247 status_file_url = status_file_url.rstrip("\n")
248
249 # Print status file path/url to stdout and to status file.
250 with open(status_file_path, "w+") as status_file:
251 if show_url:
252 print_var(status_file_url)
253 status_file.write(sprint_var(status_file_url))
254 else:
255 print_var(status_file_path)
256 status_file.write(sprint_var(status_file_path))
257
258 if stdout:
259 if tee:
260 shell_rc = tee_func(command_string, status_file_path)
261 else:
262 shell_rc = script_func(command_string, status_file_path)
263 if show_url:
264 print_var(status_file_url)
265 else:
266 print_var(status_file_path)
267 else:
268 cmd_buf = command_string + " >> " + status_file_path + " 2>&1"
269 shell_rc, output = shell_cmd(cmd_buf, show_err=0)
270 with open(status_file_path, "a") as status_file:
271 # Append ret code string and status_file_path to end of status
272 # file.
273 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
274
275 # Append status_file_path print statement to end of status file.
276 with open(status_file_path, "a") as status_file:
277 if show_url:
278 status_file.write(sprint_var(status_file_url))
279 else:
280 status_file.write(sprint_var(status_file_path))
281 exit(shell_rc)
282
283
284main()