From 193c996c0df943f6bd955bdb475548b04ec9e711 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Wed, 17 Jun 2009 00:37:35 -0400 Subject: [PATCH] Convert migrate to logger, and misc refactoring. Signed-off-by: Edward Z. Yang --- README | 2 +- TODO | 1 + bin/wizard | 4 +- lib/wizard/__init__.py | 34 +++++++++ lib/wizard/command/__init__.py | 6 +- lib/wizard/command/info.py | 2 +- lib/wizard/command/migrate.py | 122 ++++++++++++++++++++++----------- lib/wizard/command/summary.py | 2 +- lib/wizard/shell.py | 22 +++--- 9 files changed, 139 insertions(+), 56 deletions(-) diff --git a/README b/README index 4f95d69..fe3e9aa 100644 --- a/README +++ b/README @@ -6,4 +6,4 @@ locations: - lib/wizard/command/__init__.py Add the line "import commandname" - lib/wizard/command/commandname.py - Implement your command there as main() + Implement your command there as commandname() diff --git a/TODO b/TODO index 2a1460c..0528498 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,7 @@ The Git Autoinstaller TODO NOW: +- For the long running programs, I definitely want to use logging. - Whiteboard the flow for performing an upgrade on a single install. How assisted does it need to be? - Conduct migration tool testing diff --git a/bin/wizard b/bin/wizard index 916a292..4a204e8 100755 --- a/bin/wizard +++ b/bin/wizard @@ -48,10 +48,10 @@ See '%prog help COMMAND' for more information on a specific command.""" raise SystemExit(-1) # Dispatch commands try: - command_module = getattr(wizard.command, command) + command_fn = getattr(wizard.command, command) except AttributeError: parser.error("invalid action") - command_module.main(rest_argv, options) + command_fn(rest_argv, options) if __name__ == "__main__": main() diff --git a/lib/wizard/__init__.py b/lib/wizard/__init__.py index e69de29..b0c216c 100644 --- a/lib/wizard/__init__.py +++ b/lib/wizard/__init__.py @@ -0,0 +1,34 @@ +import logging +import sys +import optparse + +def makeLogger(options): + logger = logging.getLogger("main") + logger.setLevel(logging.INFO) + stdout = logging.StreamHandler(sys.stdout) + logger.addHandler(stdout) + if options.verbose: + logger.verbose = True + stdout.setLevel(logging.INFO) + else: + stdout.setLevel(logging.ERROR) + return logger + +class UserException(Exception): + """User friendly exceptions inherit from here""" + pass + +class NullLogHandler(logging.Handler): + """Log handler that doesn't do anything""" + def emit(self, record): + pass + +class WizardOptionParser(optparse.OptionParser): + """Configures some default user-level options""" + def __init__(self, *args, **kwargs): + optparse.OptionParser.__init__(self, *args, **kwargs) + self.add_option("-v", "--verbose", dest="verbose", action="store_true", + default=False, help="Turns on verbose output") + def parse_all(self, argv, logger): + options, numeric_args = self.parse_args(argv) + return options, numeric_args, makeLogger(options) diff --git a/lib/wizard/command/__init__.py b/lib/wizard/command/__init__.py index 96e12e8..c6e878d 100644 --- a/lib/wizard/command/__init__.py +++ b/lib/wizard/command/__init__.py @@ -1,3 +1,3 @@ -import info -import migrate -import summary +from info import info +from migrate import migrate +from summary import summary diff --git a/lib/wizard/command/info.py b/lib/wizard/command/info.py index a04880a..ec4331e 100644 --- a/lib/wizard/command/info.py +++ b/lib/wizard/command/info.py @@ -11,7 +11,7 @@ def indent(text, indent): # There should be a built-in return "\n".join([" " * indent + x for x in text.split("\n")]) -def main(argv, global_options): +def info(argv, global_options): usage = """usage: %prog info [ARGS] DIR Prints information about an autoinstalled directory, diff --git a/lib/wizard/command/migrate.py b/lib/wizard/command/migrate.py index fb38ff7..dbf93d3 100644 --- a/lib/wizard/command/migrate.py +++ b/lib/wizard/command/migrate.py @@ -2,70 +2,120 @@ import optparse import sys import os import shutil +import logging.handlers +from wizard import * import wizard.deploy as wd import wizard.shell as sh -def main(argv, global_options): +class PermissionsError(UserException): + def __init__(self, dir): + self.dir = dir + def __str__(self): + return """ + +ERROR: You don't have permissions to access this directory. +Do you have tickets 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'. We recommend getting +root tickets using Nelson Elhage's krbroot script +at /mit/nelhage/Public/krbroot (for which you run +'krbroot shell' and then 'aklog'). +""" + +class NoSuchDirectoryError(UserException): + def __init__(self, dir): + self.dir = dir + def __str__(self): + return """ + +ERROR: No such directory... check your typing +""" + +class AlreadyMigratedError(UserException): + def __init__(self, dir): + self.dir = dir + def __str__(self): + return """ + +ERROR: Directory already contains a .git directory. +Did you already migrate it? +""" + +class NotAutoinstallError(UserException): + def __init__(self, dir): + self.dir = dir + def __str__(self): + return """ + +ERROR: Could not find .scripts-version file. Are you sure +this is an autoinstalled application? +""" + +class NoRepositoryError(UserException): + def __init__(self, app): + self.app = app + def __str__(self): + return """ + +ERROR: Could not find repository for this application. Have +you converted the repository over? Is the name %s +the same as the name of the .git folder? +""" % self.app + +class NoTagError(UserException): + def __init__(self, version): + self.version = version + def __str__(self): + return """ + +ERROR: Could not find tag v%s-scripts in repository +for %s. Double check and make sure +the repository was prepared with all necessary tags! +""" % (self.version.version, self.version.application.name) + +def migrate(argv, global_options, logger = None): usage = """usage: %prog migrate [ARGS] DIR Migrates a directory to our Git-based autoinstall format. Performs basic sanity checking and intelligently determines what repository and tag to use.""" - parser = optparse.OptionParser(usage) - parser.add_option("-v", "--verbose", dest="verbose", action="store_true", - default=False, help="Print all commands and outputs") + parser = WizardOptionParser(usage) parser.add_option("--dry-run", dest="dry_run", action="store_true", default=False, help="Prints would would be run without changing anything") - options, args = parser.parse_args(argv) + options, args, logger = parser.parse_all(argv, logger) if len(args) > 1: parser.error("too many arguments") elif not args: parser.error("must specify directory") dir = args[0] - print "Changing working directory to autoinstall directory" try: os.chdir(dir) except OSError as e: if e.errno == 13: - print - print "ERROR: You don't have permissions to access this directory." - print "Do you have tickets for AFS with your root instance, and" - print "is your root instance on scripts-security-upd?" - print - print "You can check by running the commands 'klist' and" - print "'blanche scripts-security-upd'. We recommend getting" - print "root tickets using Nelson Elhage's krbroot script" - print "at /mit/nelhage/Public/krbroot (for which you run" - print "'krbroot shell' and then 'aklog')." - raise SystemExit(-1) + raise PermissionsError(dir) elif e.errno == 2: - print - print "ERROR: No such directory... check your typing" - raise SystemExit(-1) + raise NoSuchDirectoryError(dir) else: raise e + if os.path.isdir(".git"): + raise AlreadyMigratedError(dir) try: deploy = wd.Deployment.fromDir(".") version = deploy.getAppVersion() except IOError as e: if e.errno == 2: - print - print "ERROR: Could not find .scripts-version file. Are you sure" - print "this is an autoinstalled application?" - raise SystemExit(-1) + raise NotAutoinstallError(dir) else: raise e # calculate the repository we'll be pulling out of application = version.application app = application.name repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/wizard/srv", app + ".git") if not os.path.isdir(repo): - print - print "ERROR: Could not find repository for this application. Have" - print "you converted the repository over? Is the name %s" % app - print "the same as the the name of the foo.git folder?" - raise SystemExit(-1) + raise NoRepositoryError(app) # begin the command line process - shell = sh.Shell(options.verbose, options.dry_run) + shell = sh.Shell(logger, options.dry_run) # check if the version we're trying to convert exists. We assume # a convention here, namely, v1.2.3-scripts is what we want. If # you broke the convention... shame on you. @@ -73,11 +123,7 @@ what repository and tag to use.""" tag = "v%s-scripts" % version.version shell.call("git", "--git-dir", repo, "rev-parse", tag) except sh.CalledProcessError: - print - print "ERROR: Could not find tag v%s-scripts for" % version.version - print "this application's version. Double check and make sure" - print "the repository was prepared with all necessary tags!" - raise SystemExit(-1) + raise NoTagError(version) did_git_init = False did_git_checkout_scripts = False try: @@ -98,16 +144,14 @@ what repository and tag to use.""" did_git_checkout_scripts = True # XXX: setup .scripts/version??? # for verbose purposes, give us a git status and git diff + raise NotImplementedError if options.verbose: shell.call("git", "status") shell.call("git", "diff") except: - print - print "ERROR: Exception detected! Rolling back..." + logger.error("ERROR: Exception detected! Rolling back...") if did_git_init: - print "Deleting .git directory" shell.call("rm", "-Rf", ".git") if did_git_checkout_scripts: - print "Deleting .scripts directory" shell.call("rm", "-Rf", ".scripts") raise diff --git a/lib/wizard/command/summary.py b/lib/wizard/command/summary.py index cc05bb6..c84348b 100644 --- a/lib/wizard/command/summary.py +++ b/lib/wizard/command/summary.py @@ -34,7 +34,7 @@ class Printer(object): self._hang() print str -def main(argv, global_options): +def summary(argv, global_options): usage = """usage: %prog summary [ARGS] APPS Scans all of the collected data from parallel-find.pl, and diff --git a/lib/wizard/shell.py b/lib/wizard/shell.py index a7dee83..40b09e6 100644 --- a/lib/wizard/shell.py +++ b/lib/wizard/shell.py @@ -1,24 +1,28 @@ import subprocess -from subprocess import CalledProcessError +from subprocess import CalledProcessError, PIPE, STDOUT import sys class Shell(object): """An advanced shell, with the ability to do dry-run and log commands""" - def __init__(self, verbose = False, dry = False): - """ `verbose` Whether or not to print the command and outputs + def __init__(self, logger = False, dry = False): + """ `logger` The logger `dry` Whether or not to not run any commands, and just print""" - self.verbose = verbose + self.logger = logger self.dry = dry def call(self, *args): - if self.dry or self.verbose: - print "$ " + ' '.join(args) + if self.dry or self.logger: + self.logger.info("$ " + ' '.join(args)) if self.dry: return proc = None - if self.verbose: - proc = subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr) + if self.logger: + if hasattr(self.logger, "verbose"): + proc = subprocess.Popen(args, stdout=sys.stdout, stderr=sys.stderr) + else: + proc = subprocess.Popen(args, stdout=PIPE, stderr=STDOUT) else: proc = subprocess.Popen(args) - proc.communicate() + stdout, _ = proc.communicate() + if self.logger and stdout: self.logger.info(stdout) if proc.returncode: raise CalledProcessError(proc.returncode, args) -- 2.45.0