blob: 3d3acc38f6ffc8f9dacde667fc4a01e83044f3aa [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"""
103 Execute whenever the program ends normally or with the signals that we
104 catch (i.e. TERM, INT).
105 """
106
107 dprint_executing()
108 dprint_var(signal_number)
109
110 qprint_pgm_footer()
111
112
113def signal_handler(signal_number,
114 frame):
115 r"""
116 Handle signals. Without a function to catch a SIGTERM or SIGINT, our
117 program would terminate immediately with return code 143 and without
118 calling our exit_function.
119 """
120
121 # Our convention is to set up exit_function with atexit.register() so
122 # there is no need to explicitly call exit_function from here.
123
124 dprint_executing()
125
126 # Calling exit prevents us from returning to the code that was running
127 # when we received the signal.
128 exit(0)
129
130
131def validate_parms():
132 r"""
133 Validate program parameters, etc.
134 """
135
136 global status_dir_path
137 global command_string
138
139 # Convert command_string from list to string.
140 command_string = " ".join(command_string)
141 set_pgm_arg(command_string)
142 valid_value(command_string)
143
144 if status_dir_path == "":
145 status_dir_path = \
146 os.environ.get("STATUS_DIR_PATH",
147 os.environ.get("HOME") + "/status/")
148 status_dir_path = add_trailing_slash(status_dir_path)
149 set_pgm_arg(status_dir_path)
150 valid_dir_path(status_dir_path)
151
152 global prefix
153 global status_file_name
154 if status_file_name == "":
155 if prefix == "":
156 prefix = command_string.split(" ")[0]
Michael Walsh64fbc9c2019-04-05 16:20:26 -0500157 # File extensions (e.g. ".sh", ".py", .etc), look clumsy in
158 # status file names.
159 extension_regex = "\\.[a-zA-Z0-9]{1,3}$"
160 prefix = re.sub(extension_regex, "", prefix)
Michael Walsh888be1f2019-03-26 12:02:36 -0500161 set_pgm_arg(prefix)
162 status_file_name = prefix + "." + file_date_time_stamp() + ".status"
163 set_pgm_arg(status_file_name)
164
165 global status_file_path
166
167 status_file_path = status_dir_path + status_file_name
168 # Set environment variable for the benefit of child programs.
169 os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path
170 # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value.
171 os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path
172
173 gen_post_validation(exit_function, signal_handler)
174
175
176def script_func(command_string, status_file_path):
177 r"""
178 Run the command string producing both stdout and file output via the
179 script command and return the shell_rc.
180
181 Description of argument(s):
182 command_string The command string to be run.
183 status_file_path The path to the status file which is to
184 contain a copy of all stdout.
185 """
186
187 cmd_buf = "script -a -q -f " + status_file_path + " -c '" \
188 + escape_bash_quotes(command_string) + " ; printf \"\\n" \
189 + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'"
190 qprint_issuing(cmd_buf)
191 sub_proc = subprocess.Popen(cmd_buf, shell=True)
192 sub_proc.communicate()
193 shell_rc = sub_proc.returncode
194
195 # Retrieve return code by examining ret_code_str output statement from
196 # status file.
197 # Example text to be analyzed.
198 # auto_status_file_ret_code: 127
199 cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \
200 + ret_code_str + ":[ ]+\""
201 rc, output = shell_cmd(cmd_buf)
202 key, value = parse_key_value(output)
203 shell_rc = int(value)
204
205 return shell_rc
206
207
208def tee_func(command_string, status_file_path):
209 r"""
210 Run the command string producing both stdout and file output via the tee
211 command and return the shell_rc.
212
213 Description of argument(s):
214 command_string The command string to be run.
215 status_file_path The path to the status file which is to
216 contain a copy of all stdout.
217 """
218
219 cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \
220 + status_file_path
221 qprint_issuing(cmd_buf)
222 sub_proc = subprocess.Popen(cmd_buf, shell=True)
223 sub_proc.communicate()
224 shell_rc = sub_proc.returncode
225
226 print
227 print_varx(ret_code_str, shell_rc)
228 with open(status_file_path, "a") as status_file:
229 # Append ret code string and status_file_path to end of status file.
230 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
231
232 return shell_rc
233
234
235def main():
236
237 gen_get_options(parser, stock_list)
238
239 validate_parms()
240
241 qprint_pgm_header()
242
243 global ret_code_str
244 ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code"
245
246 global show_url
247 if show_url:
248 shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0)
249 if shell_rc != 0:
250 show_url = 0
251 set_pgm_arg(show_url)
252 else:
253 shell_rc, status_file_url = shell_cmd("get_file_path_url.py "
254 + status_file_path)
255 status_file_url = status_file_url.rstrip("\n")
256
257 # Print status file path/url to stdout and to status file.
258 with open(status_file_path, "w+") as status_file:
259 if show_url:
260 print_var(status_file_url)
261 status_file.write(sprint_var(status_file_url))
262 else:
263 print_var(status_file_path)
264 status_file.write(sprint_var(status_file_path))
265
266 if stdout:
267 if tee:
268 shell_rc = tee_func(command_string, status_file_path)
269 else:
270 shell_rc = script_func(command_string, status_file_path)
271 if show_url:
272 print_var(status_file_url)
273 else:
274 print_var(status_file_path)
275 else:
276 cmd_buf = command_string + " >> " + status_file_path + " 2>&1"
277 shell_rc, output = shell_cmd(cmd_buf, show_err=0)
278 with open(status_file_path, "a") as status_file:
279 # Append ret code string and status_file_path to end of status
280 # file.
281 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
282
283 # Append status_file_path print statement to end of status file.
284 with open(status_file_path, "a") as status_file:
285 if show_url:
286 status_file.write(sprint_var(status_file_url))
287 else:
288 status_file.write(sprint_var(status_file_path))
289 exit(shell_rc)
290
291
292main()