From 409a06640fa9d4b8b7e0da0219b3b6fd7b817685 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Wed, 29 Jul 2009 23:10:07 -0400 Subject: [PATCH] Refactor log code to its own module. Signed-off-by: Edward Z. Yang --- TODO | 8 +- wizard/command/info.py | 11 ++- wizard/deploy.py | 210 ++++++----------------------------------- wizard/log.py | 162 +++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 190 deletions(-) create mode 100644 wizard/log.py diff --git a/TODO b/TODO index ed8fe44..387e8bb 100644 --- a/TODO +++ b/TODO @@ -5,10 +5,6 @@ TODO NOW: - Better error message if daemon/scripts-security-upd is not on scripts-security-upd list -- Refactor log model out of deploy -- Refactor @staticmethod's for building from parallel-find to factory -- Refactor get*() into properties - - Add repository flag to migrate so that we can specify an arbitrary repository to migrate to @@ -22,6 +18,10 @@ TODO NOW: - Turn on mediawiki new autoinstaller - Migrate all mediawiki installs +- Testing: + - Need a scriptable autoinstaller, which means we rewrite + all of the autoinstall machinery + - Consider making usermode wizard operation a support mode (mostly for letting users upgrade things themself) diff --git a/wizard/command/info.py b/wizard/command/info.py index 2673bac..04ed70d 100644 --- a/wizard/command/info.py +++ b/wizard/command/info.py @@ -3,10 +3,11 @@ import sys import subprocess from wizard import deploy +from wizard import log def main(argv, baton): options, args = parse_args(argv) - d = deploy.Deployment.fromDir(args[0]) + d = deploy.Deployment(args[0]) d.log # force the log to be loaded, to pre-empt errors with PipeToLess(): print_log(d, options) @@ -28,16 +29,16 @@ including its history and current version.""" def print_log(d, options): if options.reverse: - log = reversed(d.log) + dlog = reversed(d.log) else: - log = d.log - for entry in log: + dlog = d.log + for entry in dlog: print "%s %s" % (entry.version.application.name, entry.version.version) print "User: %s" % entry.user print "Date: %s" % entry.datetime.strftime("%a %b %0d %H:%M:%S %Y %z") print info = "Unknown" - if isinstance(entry.source, deploy.TarballInstall): + if isinstance(entry.source, log.TarballInstall): info = "Installed with tarball at:\n%s" % \ prettify(entry.source.location) print indent(info, 4) diff --git a/wizard/deploy.py b/wizard/deploy.py index 9e5697b..a5a5ee1 100644 --- a/wizard/deploy.py +++ b/wizard/deploy.py @@ -4,6 +4,9 @@ import dateutil.parser import distutils.version import wizard +from wizard import log + +## -- Global Functions -- def getInstallLines(vs): """Retrieves a list of lines from the version directory that @@ -12,6 +15,8 @@ def getInstallLines(vs): return fileinput.input([vs]) return fileinput.input([vs + "/" + f for f in os.listdir(vs)]) +## -- Model Objects -- + class Deployment(object): """Represents a deployment of an autoinstall; i.e. a concrete directory in web_scripts that has .scripts-version in it.""" @@ -22,22 +27,6 @@ class Deployment(object): self.location = location self._version = version self._log = log - # XXX: factory - @staticmethod - def parse(line): - """Parses a line from the results of parallel-find.pl. - This will work out of the box with fileinput, see - getInstallLines()""" - line = line.rstrip() - try: - location, deploydir = line.split(":") - except ValueError: - return Deployment(line) # lazy loaded version - return Deployment(location, version=ApplicationVersion.parse(deploydir, location)) - @staticmethod - def fromDir(dir): - """Lazily creates a deployment from a directory""" - return Deployment(dir) @property def version_file(self): return os.path.join(self.location, '.scripts-version') @@ -47,7 +36,7 @@ class Deployment(object): @property def log(self): if not self._log: - self._log = DeployLog.load(self.version_file) + self._log = log.DeployLog.load(self.version_file) return self._log @property def version(self): @@ -58,6 +47,17 @@ class Deployment(object): """Returns the ApplicationVersion of the deployment""" if self._version and not force: return self._version else: return self.log[-1].version + @staticmethod + def parse(line): + """Parses a line from the results of parallel-find.pl. + This will work out of the box with fileinput, see + getInstallLines()""" + line = line.rstrip() + try: + location, deploydir = line.split(":") + except ValueError: + return Deployment(line) # lazy loaded version + return Deployment(location, version=ApplicationVersion.parse(deploydir, location)) class Application(object): """Represents the generic notion of an application, i.e. @@ -77,125 +77,6 @@ class Application(object): self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self) return self.versions[version] -class DeployLog(list): - # As per #python; if you decide to start overloading magic methods, - # we should remove this subclass - """Equivalent to .scripts-version: a series of DeployRevisions.""" - def __init__(self, revs = []): - """`revs` List of DeployRevision objects""" - list.__init__(self, revs) # pass to list - # XXX: factory - @staticmethod - def load(file): - """Loads a scripts version file and parses it into - DeployLog and DeployRevision objects""" - # XXX: DIRTY DIRTY HACK - # What we should actually do is parse the git logs - scriptsdir = os.path.join(os.path.dirname(file), ".scripts") - if os.path.isdir(scriptsdir): - file = os.path.join(scriptsdir, "old-version") - i = 0 - rev = DeployRevision() - revs = [] - def append(rev): - if i: - if i != 4: - raise ScriptsVersionNotEnoughFieldsError(file) - revs.append(rev) - try: - fh = open(file) - except IOError: - raise ScriptsVersionNoSuchFile(file) - for line in fh: - line = line.rstrip() - if not line: - append(rev) - i = 0 - rev = DeployRevision() - continue - if i == 0: - # we need the dateutil parser in order to - # be able to parse time offsets - rev.datetime = dateutil.parser.parse(line) - elif i == 1: - rev.user = line - elif i == 2: - rev.source = DeploySource.parse(line) - elif i == 3: - rev.version = ApplicationVersion.parse(line, rev.source) - else: - # ruh oh - raise ScriptsVersionTooManyFieldsError(file) - i += 1 - append(rev) - return DeployLog(revs) - def __repr__(self): - return '' % list.__repr__(self) - -class DeployRevision(object): - """A single entry in the .scripts-version file. Contains who deployed - this revision, what application version this is, etc.""" - def __init__(self, datetime=None, user=None, source=None, version=None): - """ `datetime` Time this revision was deployed - `user` Person who deployed this revision, in user@host format. - `source` Instance of DeploySource - `version` Instance of ApplicationVersion - Note: This object is typically built incrementally.""" - self.datetime = datetime - self.user = user - self.source = source - self.version = version - -class DeploySource(object): - """Source of the deployment; see subclasses for examples""" - def __init__(self): - raise NotImplementedError # abstract class - # XXX: factory - @staticmethod - def parse(line): - # munge out common prefix - rel = os.path.relpath(line, "/afs/athena.mit.edu/contrib/scripts/") - parts = rel.split("/") - if parts[0] == "wizard": - return WizardUpdate() - elif parts[0] == "deploy" or parts[0] == "deploydev": - isDev = ( parts[0] == "deploydev" ) - try: - if parts[1] == "updates": - return OldUpdate(isDev) - else: - return TarballInstall(line, isDev) - except IndexError: - pass - return UnknownDeploySource(line) - -class TarballInstall(DeploySource): - """Original installation from tarball, characterized by - /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z.tar.gz - """ - def __init__(self, location, isDev): - self.location = location - self.isDev = isDev - -class OldUpdate(DeploySource): - """Upgrade using old upgrade infrastructure, characterized by - /afs/athena.mit.edu/contrib/scripts/deploydev/updates/update-scripts-version.pl - """ - def __init__(self, isDev): - self.isDev = isDev - -class WizardUpdate(DeploySource): - """Upgrade using wizard infrastructure, characterized by - /afs/athena.mit.edu/contrib/scripts/wizard/bin/wizard HASHGOBBLEDYGOOK - """ - def __init__(self): - pass - -class UnknownDeploySource(DeploySource): - """Deployment that we don't know the meaning of. Wot!""" - def __init__(self, line): - self.line = line - class ApplicationVersion(object): """Represents an abstract notion of a version for an application""" def __init__(self, version, application): @@ -214,7 +95,6 @@ class ApplicationVersion(object): return "v%s-scripts" % self.version def __cmp__(x, y): return cmp(x.version, y.version) - # XXX: move to factory @staticmethod def parse(deploydir,location,applookup=None): # The version of the deployment, will be: @@ -239,24 +119,11 @@ class ApplicationVersion(object): except KeyError: raise NoSuchApplication(app, location) -class Error(wizard.Error): - """Base error class for deploy errors""" - def __init__(self, location): - self.location = location - def __str__(self): - return "ERROR: Generic error at %s" % self.location - -class NoRepositoryError(Error): - def __init__(self, app): - self.app = app - self.location = "unknown" - def __str__(self): - return """ +## -- Exceptions -- -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 Error(Exception): + """Base error class for this module""" + pass class NoSuchApplication(Error): def __init__(self, name, location): @@ -272,36 +139,17 @@ class DeploymentParseError(Error): def __str__(self): return """ERROR: Unparseable '%s' at %s""" % (self.malformed, self.location) -class ScriptsVersionError(Error): - """Errors specific to the parsing of a full .scripts-version file - (errors that could also be triggered while parsing a parallel-find - output should not be this subclass.)""" - pass - -class ScriptsVersionTooManyFieldsError(ScriptsVersionError): - def __str__(self): - return """ - -ERROR: Could not parse .scripts-version file. It -contained too many fields. -""" - -class ScriptsVersionNotEnoughFieldsError(ScriptsVersionError): - def __str__(self): - return """ - -ERROR: Could not parse .scripts-version file. It -didn't contain enough fields. -""" - -class ScriptsVersionNoSuchFile(ScriptsVersionError): - def __init__(self, file): - self.file = file +class NoRepositoryError(Error): + def __init__(self, app): + self.app = app + self.location = "unknown" def __str__(self): return """ -ERROR: File %s didn't exist. -""" % self.file +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 # If you want, you can wrap this up into a registry and access things # through that, but it's not really necessary diff --git a/wizard/log.py b/wizard/log.py new file mode 100644 index 0000000..f251b01 --- /dev/null +++ b/wizard/log.py @@ -0,0 +1,162 @@ +import os.path +import dateutil.parser + +import wizard.deploy # otherwise circular dep is unhappy + +# This code operates off of the assumption of .scripts-version, which +# is not true. + +class DeployLog(list): + # As per #python; if you decide to start overloading magic methods, + # we should remove this subclass + """Equivalent to .scripts-version: a series of DeployRevisions.""" + def __init__(self, revs = []): + """`revs` List of DeployRevision objects""" + list.__init__(self, revs) # pass to list + def __repr__(self): + return '' % list.__repr__(self) + @staticmethod + def load(file): + """Loads a scripts version file and parses it into + DeployLog and DeployRevision objects""" + # XXX: DIRTY DIRTY HACK + # What we should actually do is parse the git logs + scriptsdir = os.path.join(os.path.dirname(file), ".scripts") + if os.path.isdir(scriptsdir): + file = os.path.join(scriptsdir, "old-version") + i = 0 + rev = DeployRevision() + revs = [] + def append(rev): + if i: + if i != 4: + raise ScriptsVersionNotEnoughFieldsError(file) + revs.append(rev) + try: + fh = open(file) + except IOError: + raise ScriptsVersionNoSuchFile(file) + for line in fh: + line = line.rstrip() + if not line: + append(rev) + i = 0 + rev = DeployRevision() + continue + if i == 0: + # we need the dateutil parser in order to + # be able to parse time offsets + rev.datetime = dateutil.parser.parse(line) + elif i == 1: + rev.user = line + elif i == 2: + rev.source = DeploySource.parse(line) + elif i == 3: + rev.version = wizard.deploy.ApplicationVersion.parse(line, rev.source) + else: + # ruh oh + raise ScriptsVersionTooManyFieldsError(file) + i += 1 + append(rev) + return DeployLog(revs) + +class DeployRevision(object): + """A single entry in the .scripts-version file. Contains who deployed + this revision, what application version this is, etc.""" + def __init__(self, datetime=None, user=None, source=None, version=None): + """ `datetime` Time this revision was deployed + `user` Person who deployed this revision, in user@host format. + `source` Instance of DeploySource + `version` Instance of ApplicationVersion + Note: This object is typically built incrementally.""" + self.datetime = datetime + self.user = user + self.source = source + self.version = version + +class DeploySource(object): + """Source of the deployment; see subclasses for examples""" + def __init__(self): + raise NotImplementedError # abstract class + @staticmethod + def parse(line): + # munge out common prefix + rel = os.path.relpath(line, "/afs/athena.mit.edu/contrib/scripts/") + parts = rel.split("/") + if parts[0] == "wizard": + return WizardUpdate() + elif parts[0] == "deploy" or parts[0] == "deploydev": + isDev = ( parts[0] == "deploydev" ) + try: + if parts[1] == "updates": + return OldUpdate(isDev) + else: + return TarballInstall(line, isDev) + except IndexError: + pass + return UnknownDeploySource(line) + +class TarballInstall(DeploySource): + """Original installation from tarball, characterized by + /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z.tar.gz + """ + def __init__(self, location, isDev): + self.location = location + self.isDev = isDev + +class OldUpdate(DeploySource): + """Upgrade using old upgrade infrastructure, characterized by + /afs/athena.mit.edu/contrib/scripts/deploydev/updates/update-scripts-version.pl + """ + def __init__(self, isDev): + self.isDev = isDev + +class WizardUpdate(DeploySource): + """Upgrade using wizard infrastructure, characterized by + /afs/athena.mit.edu/contrib/scripts/wizard/bin/wizard HASHGOBBLEDYGOOK + """ + def __init__(self): + pass + +class UnknownDeploySource(DeploySource): + """Deployment that we don't know the meaning of. Wot!""" + def __init__(self, line): + self.line = line + +## -- Exceptions -- + +class Error(Exception): + """Base error class for log errors.""" + pass + +class ScriptsVersionError(Error): + """Errors specific to the parsing of a full .scripts-version file + (errors that could also be triggered while parsing a parallel-find + output should not be this subclass.)""" + pass + +class ScriptsVersionTooManyFieldsError(ScriptsVersionError): + def __str__(self): + return """ + +ERROR: Could not parse .scripts-version file. It +contained too many fields. +""" + +class ScriptsVersionNotEnoughFieldsError(ScriptsVersionError): + def __str__(self): + return """ + +ERROR: Could not parse .scripts-version file. It +didn't contain enough fields. +""" + +class ScriptsVersionNoSuchFile(ScriptsVersionError): + def __init__(self, file): + self.file = file + def __str__(self): + return """ + +ERROR: File %s didn't exist. +""" % self.file + -- 2.45.2