]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/command/__init__.py
Implement MediaWiki scaffolding for auto conflict resolution; untested.
[wizard.git] / wizard / command / __init__.py
index e5cb0b4354f06b3d3c85b2ab9d43c92aa8e16ab8..bc1b718b3ba8f0ad02cebbc2b25682a5a1e56c45 100644 (file)
@@ -1,79 +1,62 @@
 import logging
+import traceback
 import os
 import sys
 import optparse
 import errno
+import pwd
 
 import wizard
+from wizard import util
 
 logging_setup = False
 
-class Error(wizard.Error):
-    """Base error class for all command errors"""
-    pass
-
-class PermissionsError(Error):
-    def __init__(self, dir):
-        self.dir = dir
-    def __str__(self):
-        return """
-
-ERROR: You don't have permissions to access this directory.
-Do you have tokens for AFS with your root instance, and
-is your root instance on scripts-security-upd?
-
-You can check by running the commands 'klist' and
-'blanche scripts-security-upd'.
-"""
+def boolish(val):
+    """
+    Parse the contents of an environment variable as a boolean.
+    This recognizes more values as ``False`` than :func:`bool` would.
 
-class NoSuchDirectoryError(Error):
-    def __init__(self, dir):
-        self.dir = dir
-    def __str__(self):
-        return """
-
-ERROR: No such directory... check your typing
-"""
-
-def chdir(dir):
+        >>> boolish("0")
+        False
+        >>> boolish("no")
+        False
+        >>> boolish("1")
+        True
+    """
     try:
-        os.chdir(dir)
-    except OSError as e:
-        if e.errno == errno.EACCES:
-            raise PermissionsError(dir)
-        elif e.errno == errno.ENOENT:
-            raise NoSuchDirectoryError(dir)
-        else: raise e
+        return bool(int(val))
+    except (ValueError, TypeError):
+        if val == "No" or val == "no" or val == "false" or val == "False":
+            return False
+        return bool(val)
 
 def makeLogger(options, numeric_args):
     global logging_setup
     if logging_setup: return logging.getLogger()
-    context = " ".join(numeric_args)
     logger = logging.getLogger()
+    logger.handlers = [] # under certain cases, a spurious stream handler is set. We don't know why
     logger.setLevel(logging.INFO)
     stderr = logging.StreamHandler(sys.stderr)
-    stderr.setFormatter(logging.Formatter(" " * int(options.indent) + '%(levelname)s: %(message)s'))
-    dateFormat = "%H:%M:%S"
-    if options.context:
-        logformatter = logging.Formatter("%(asctime)s %(levelname)s -- " + context + ": %(message)s", dateFormat)
-    else:
-        logformatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", dateFormat)
+    stderr.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
     if not options.quiet: logger.addHandler(stderr)
     else: logger.addHandler(NullLogHandler()) # prevent default
     if options.log_file:
         file = logging.FileHandler(options.log_file)
+        logformatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")
         file.setFormatter(logformatter)
         logger.addHandler(file)
-        if options.log_file_chmod:
-            os.chmod(options.log_file, int(options.log_file_chmod, 8))
     if options.debug:
         logger.setLevel(logging.DEBUG)
     else:
         stderr.setLevel(logging.WARNING)
-        if options.verbose or hasattr(options, "dry_run"):
+        if options.verbose:
             stderr.setLevel(logging.INFO)
         if options.log_file:
             file.setLevel(logging.INFO)
+    def our_excepthook(type, value, tb):
+        logging.error("".join(traceback.format_exception(type,value,tb)))
+        sys.exit(1)
+    sys.excepthook = our_excepthook
     logging_setup = True
     return logger
 
@@ -81,23 +64,74 @@ def makeBaseArgs(options, **grab):
     """Takes parsed options, and breaks them back into a command
     line string that we can pass into a subcommand"""
     args = []
-    grab["log_file"]= "--log-file"
     grab["debug"]   = "--debug"
     grab["verbose"] = "--verbose"
-    grab["indent"]  = "--indent"
     grab["quiet"]   = "--quiet"
     #grab["log_db"] = "--log-db"
     for k,flag in grab.items():
         value = getattr(options, k)
-        if not value and k != "indent": continue
+        if not value: continue
         args.append(flag)
         if type(value) is not bool:
-            if k == "indent":
-                value += 4
             args.append(str(value))
-    args.append("--context") # always have context for a subcommand
     return args
 
+def security_check_homedir(location):
+    """
+    Performs a check against a directory to determine if current
+    directory's owner has a home directory that is a parent directory.
+    This protects against malicious mountpoints, and is roughly equivalent
+    to the suexec checks.
+    """
+    try:
+        uid = util.get_dir_uid(location)
+        real = os.path.realpath(location)
+        if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
+            logging.error("Security check failed, owner of deployment and "
+                    "owner of home directory mismatch for %s" % location)
+            return False
+    except KeyError:
+        logging.error("Security check failed, could not look up "
+                "owner of %s (uid %d)" % (location, uid))
+        return False
+    except OSError as e:
+        logging.error("OSError: %s" % str(e))
+        return False
+    return True
+
+def calculate_log_name(log_dir, i, dir):
+    """
+    Calculates a log entry given a log directory, numeric identifier, and
+    directory under operation.
+    """
+    return os.path.join(log_dir, "%04d" % i + dir.replace('/', '-') + ".log")
+
+def open_logs(log_dir, log_names=('warnings', 'errors')):
+    """
+    Opens a number of log files for auxiliary reporting.  You can override what
+    log files to generate using ``log_names``, which corresponds to the tuple
+    of log files you will receive, i.e. the default returns a tuple
+    ``(warnings.log file object, errors.log file object)``.
+
+    .. note::
+
+        The log directory is chmod'ed 777 after creation, to enable
+        de-priviledged processes to create files.
+    """
+    # must not be on AFS, since subprocesses won't be
+    # able to write to the logfiles do the to the AFS patch.
+    try:
+        os.mkdir(log_dir)
+    except OSError as e:
+        if e.errno != errno.EEXIST:
+            raise
+        #if create_subdirs:
+        #    log_dir = os.path.join(log_dir, str(int(time.time())))
+        #    os.mkdir(log_dir) # if fails, be fatal
+        #    # XXX: update last symlink
+    os.chmod(log_dir, 0o777)
+    return (open(os.path.join(os.path.join(log_dir, "%s.log" % x)), "a") for x in log_names)
+
 class NullLogHandler(logging.Handler):
     """Log handler that doesn't do anything"""
     def emit(self, record):
@@ -112,19 +146,13 @@ class WizardOptionParser(optparse.OptionParser):
         self.add_option("-h", "--help", action="help", help=optparse.SUPPRESS_HELP)
         group = optparse.OptionGroup(self, "Common Options")
         group.add_option("-v", "--verbose", dest="verbose", action="store_true",
-                default=False, help="Turns on verbose output")
+                default=boolish(os.getenv("WIZARD_VERBOSE")), help="Turns on verbose output.  Envvar is WIZARD_VERBOSE")
         group.add_option("--debug", dest="debug", action="store_true",
-                default=False, help="Turns on debugging output")
+                default=boolish(os.getenv("WIZARD_DEBUG")), help="Turns on debugging output.  Envvar is WIZARD_DEBUG")
         group.add_option("-q", "--quiet", dest="quiet", action="store_true",
-                default=False, help="Turns off output to stdout")
-        group.add_option("--log-file", dest="log_file",
+                default=boolish(os.getenv("WIZARD_QUIET")), help="Turns off output to stdout. Envvar is WIZARD_QUIET")
+        group.add_option("--log-file", dest="log_file", metavar="FILE",
                 default=None, help="Logs verbose output to file")
-        group.add_option("--log-file-chmod", dest="log_file_chmod",
-                default=None, help="Chmod the log file after opening.  Number is octal. You must chmod the log file 666 and place the file in /tmp if subprocesses are running as different users.")
-        group.add_option("--indent", dest="indent",
-                default=0, help="Indents stdout, useful for nested calls")
-        group.add_option("--context", dest="context", action="store_true",
-                default=False, help="Adds context to logs, useful for parallel processing")
         self.add_option_group(group)
         options, numeric_args = self.parse_args(argv)
         makeLogger(options, numeric_args)
@@ -145,3 +173,7 @@ class OptionBaton(object):
         """Hands off parameters to option parser"""
         for key in args:
             option_parser.add_option(self.store[key])
+
+class Error(wizard.Error):
+    """Base error class for all command errors"""
+    pass