X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/3f56e5237e400234761729ddae0d0b2a9ca74085..1b75c0b4b6c16de49ce5c270ee02c8650630aae5:/wizard/command/__init__.py diff --git a/wizard/command/__init__.py b/wizard/command/__init__.py index e5cb0b4..bc1b718 100644 --- a/wizard/command/__init__.py +++ b/wizard/command/__init__.py @@ -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