New auto_status_file.py program

auto_status_file.py will create a status file path name adhering to the
following pattern: <status dir path>/<prefix>.yymmdd.hhmmss.status. It will
then run the command string and direct its stdout/stderr to the status file
and optionally to stdout. This dual output streaming will be accomplished
using either the "script" or the "tee" program. auto_status_file.py will also
set and export environment variable "AUTO_STATUS_FILE_PATH" for the benefit of
child programs.

Change-Id: I5d9fbb206e0bfc362146ba2bcc7333c8ba261a44
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/bin/auto_status_file.py b/bin/auto_status_file.py
new file mode 100755
index 0000000..0ec967c
--- /dev/null
+++ b/bin/auto_status_file.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+
+r"""
+See help text for details.
+"""
+
+import sys
+import subprocess
+
+save_path_0 = sys.path[0]
+del sys.path[0]
+
+from gen_arg import *
+from gen_print import *
+from gen_valid import *
+from gen_misc import *
+from gen_cmd import *
+from var_funcs import *
+
+# Restore sys.path[0].
+sys.path.insert(0, save_path_0)
+
+# Set exit_on_error for gen_valid functions.
+set_exit_on_error(True)
+
+parser = argparse.ArgumentParser(
+    usage='%(prog)s [OPTIONS]',
+    description="%(prog)s will create a status file path name adhering to the"
+                + " following pattern: <status dir path>/<prefix>.yymmdd."
+                + "hhmmss.status.  It will then run the command string and"
+                + " direct its stdout/stderr to the status file and optionally"
+                + " to stdout.  This dual output streaming will be"
+                + " accomplished using either the \"script\" or the \"tee\""
+                + " program.  %(prog)s will also set and export environment"
+                + " variable \"AUTO_STATUS_FILE_PATH\" for the benefit of"
+                + " child programs.",
+    formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    prefix_chars='-+')
+
+parser.add_argument(
+    '--status_dir_path',
+    default='',
+    help="The path to the directory where the status file will be created."
+         + "%(default)s The default value is obtained from environment"
+         + " variable \"${STATUS_DIR_PATH}\", if set or from \"${HOME}/"
+         + "status/\".")
+
+parser.add_argument(
+    '--prefix',
+    default='',
+    help="The prefix for the generated file name.%(default)s The default value"
+         + " is the command portion (i.e. the first token) of the command"
+         + " string.")
+
+parser.add_argument(
+    '--status_file_name',
+    default='',
+    help="This allows the user to explicitly specify the status file name.  If"
+         + " this argument is not used, %(prog)s composes a status file name."
+         + "  If this argument is specified, the \"--prefix\" argument is"
+         + " ignored.")
+
+parser.add_argument(
+    '--stdout',
+    default=1,
+    type=int,
+    choices=[1, 0],
+    help="Indicates that stdout/stderr from the command string execution"
+         + " should be written to stdout as well as to the status file.")
+
+parser.add_argument(
+    '--tee',
+    default=0,
+    type=int,
+    choices=[1, 0],
+    help="Indicates that \"tee\" rather than \"script\" should be used.")
+
+parser.add_argument(
+    '--show_url',
+    default=0,
+    type=int,
+    choices=[1, 0],
+    help="Indicates that the status file path shown should be shown in the"
+         + " form of a url.  If the output is to be viewed from a browser,"
+         + " this may well become a clickable link.  Note that the"
+         + " get_file_path_url.py program must be found in the \"PATH\""
+         + " environment variable for this argument to be effective.")
+
+parser.add_argument(
+    'command_string',
+    default='',
+    nargs='*',
+    help="The command string to be run.%(default)s")
+
+# Populate stock_list with options we want.
+stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)]
+
+
+def exit_function(signal_number=0,
+                  frame=None):
+    r"""
+    Execute whenever the program ends normally or with the signals that we
+    catch (i.e. TERM, INT).
+    """
+
+    dprint_executing()
+    dprint_var(signal_number)
+
+    qprint_pgm_footer()
+
+
+def signal_handler(signal_number,
+                   frame):
+    r"""
+    Handle signals.  Without a function to catch a SIGTERM or SIGINT, our
+    program would terminate immediately with return code 143 and without
+    calling our exit_function.
+    """
+
+    # Our convention is to set up exit_function with atexit.register() so
+    # there is no need to explicitly call exit_function from here.
+
+    dprint_executing()
+
+    # Calling exit prevents us from returning to the code that was running
+    # when we received the signal.
+    exit(0)
+
+
+def validate_parms():
+    r"""
+    Validate program parameters, etc.
+    """
+
+    global status_dir_path
+    global command_string
+
+    # Convert command_string from list to string.
+    command_string = " ".join(command_string)
+    set_pgm_arg(command_string)
+    valid_value(command_string)
+
+    if status_dir_path == "":
+        status_dir_path = \
+            os.environ.get("STATUS_DIR_PATH",
+                           os.environ.get("HOME") + "/status/")
+    status_dir_path = add_trailing_slash(status_dir_path)
+    set_pgm_arg(status_dir_path)
+    valid_dir_path(status_dir_path)
+
+    global prefix
+    global status_file_name
+    if status_file_name == "":
+        if prefix == "":
+            prefix = command_string.split(" ")[0]
+            set_pgm_arg(prefix)
+        status_file_name = prefix + "." + file_date_time_stamp() + ".status"
+        set_pgm_arg(status_file_name)
+
+    global status_file_path
+
+    status_file_path = status_dir_path + status_file_name
+    # Set environment variable for the benefit of child programs.
+    os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path
+    # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value.
+    os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path
+
+    gen_post_validation(exit_function, signal_handler)
+
+
+def script_func(command_string, status_file_path):
+    r"""
+    Run the command string producing both stdout and file output via the
+    script command and return the shell_rc.
+
+    Description of argument(s):
+    command_string                  The command string to be run.
+    status_file_path                The path to the status file which is to
+                                    contain a copy of all stdout.
+    """
+
+    cmd_buf = "script -a -q -f " + status_file_path + " -c '" \
+        + escape_bash_quotes(command_string) + " ; printf \"\\n" \
+        + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'"
+    qprint_issuing(cmd_buf)
+    sub_proc = subprocess.Popen(cmd_buf, shell=True)
+    sub_proc.communicate()
+    shell_rc = sub_proc.returncode
+
+    # Retrieve return code by examining ret_code_str output statement from
+    # status file.
+    # Example text to be analyzed.
+    # auto_status_file_ret_code:                        127
+    cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \
+        + ret_code_str + ":[ ]+\""
+    rc, output = shell_cmd(cmd_buf)
+    key, value = parse_key_value(output)
+    shell_rc = int(value)
+
+    return shell_rc
+
+
+def tee_func(command_string, status_file_path):
+    r"""
+    Run the command string producing both stdout and file output via the tee
+    command and return the shell_rc.
+
+    Description of argument(s):
+    command_string                  The command string to be run.
+    status_file_path                The path to the status file which is to
+                                    contain a copy of all stdout.
+    """
+
+    cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \
+        + status_file_path
+    qprint_issuing(cmd_buf)
+    sub_proc = subprocess.Popen(cmd_buf, shell=True)
+    sub_proc.communicate()
+    shell_rc = sub_proc.returncode
+
+    print
+    print_varx(ret_code_str, shell_rc)
+    with open(status_file_path, "a") as status_file:
+        # Append ret code string and status_file_path to end of status file.
+        status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
+
+    return shell_rc
+
+
+def main():
+
+    gen_get_options(parser, stock_list)
+
+    validate_parms()
+
+    qprint_pgm_header()
+
+    global ret_code_str
+    ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code"
+
+    global show_url
+    if show_url:
+        shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0)
+        if shell_rc != 0:
+            show_url = 0
+            set_pgm_arg(show_url)
+        else:
+            shell_rc, status_file_url = shell_cmd("get_file_path_url.py "
+                                                  + status_file_path)
+            status_file_url = status_file_url.rstrip("\n")
+
+    # Print status file path/url to stdout and to status file.
+    with open(status_file_path, "w+") as status_file:
+        if show_url:
+            print_var(status_file_url)
+            status_file.write(sprint_var(status_file_url))
+        else:
+            print_var(status_file_path)
+            status_file.write(sprint_var(status_file_path))
+
+    if stdout:
+        if tee:
+            shell_rc = tee_func(command_string, status_file_path)
+        else:
+            shell_rc = script_func(command_string, status_file_path)
+        if show_url:
+            print_var(status_file_url)
+        else:
+            print_var(status_file_path)
+    else:
+        cmd_buf = command_string + " >> " + status_file_path + " 2>&1"
+        shell_rc, output = shell_cmd(cmd_buf, show_err=0)
+        with open(status_file_path, "a") as status_file:
+            # Append ret code string and status_file_path to end of status
+            # file.
+            status_file.write("\n" + sprint_varx(ret_code_str, shell_rc))
+
+    # Append status_file_path print statement to end of status file.
+    with open(status_file_path, "a") as status_file:
+        if show_url:
+            status_file.write(sprint_var(status_file_url))
+        else:
+            status_file.write(sprint_var(status_file_path))
+    exit(shell_rc)
+
+
+main()