import os.path
import math
import fileinput
+import dateutil.parser
from distutils.version import LooseVersion as Version
def getInstallLines(global_options):
+ """Retrieves a list of lines from the version directory that
+ can be passed to Deployment.parse()"""
vd = global_options.version_dir
try:
return fileinput.input([vd + "/" + f for f in os.listdir(vd)])
pass
class Deployment(object):
+ """Represents a deployment of an autoinstall; i.e. a concrete
+ directory in web_scripts that has .scripts-version in it."""
def __init__(self, location, version):
+ # XXX: This constructor should change
self.location = location
self.version = version
self.application = version.application
@staticmethod
def parse(line):
"""Parses a line from the results of parallel-find.pl.
- This will work out of the box with fileinput"""
+ This will work out of the box with fileinput, see
+ getInstallLines()"""
try:
location, deploydir = line.rstrip().split(":")
except ValueError:
return Deployment(location, applications[app].getVersion(version))
except KeyError:
raise NoSuchApplication
+ @staticmethod
+ def fromDir(dir):
+ """Creates a deployment from a directory"""
+ version_file = os.path.join(dir, '.scripts-version')
+ # needs deployment log
def count(self):
"""Simple method which registers the deployment as a +1 on the
appropriate version. No further inspection is done."""
return False
class Application(object):
+ """Represents the generic notion of an application, i.e.
+ mediawiki or phpbb."""
+ # XXX: See below XXX
HISTOGRAM_WIDTH = 30
def __init__(self, name):
self.name = name
if version not in self.versions:
self.versions[version] = ApplicationVersion(Version(version), self)
return self.versions[version]
+ # XXX: This code should go in summary.py; maybe as a mixin, maybe as
+ # a visitor acceptor
def _graph(self, v):
return '+' * int(math.ceil(float(v)/self._max * self.HISTOGRAM_WIDTH))
def __str__(self):
ret.append("%d users have %s" % (c,f))
return "\n".join(ret)
+class DeployLog(object):
+ """Equivalent to .scripts-version: a series of DeployRevisions."""
+ def __init__(self, revs = []):
+ """`revs` List of DeployRevision objects"""
+ self.revs = revs
+ @staticmethod
+ def load(file):
+ """Loads a scripts version file and parses it into
+ DeployLog and DeployRevision objects"""
+ i = 0
+ rev = DeployRevision()
+ revs = []
+ for line in open(file):
+ line = line.rstrip()
+ if not line:
+ i = 0
+ revs.append(rev)
+ rev = DeployRevision()
+ continue
+ if i == 0:
+ 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)
+ else:
+ # ruh oh
+ pass
+ i += 1
+ if i: revs.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 NotImplemented # abstract class
+ @staticmethod
+ def parse(line):
+ raise NotImplemented
+
+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):
+ self.location = location
+
+class OldUpgrade(DeploySource):
+ """Upgrade using old upgrade infrastructure, characterized by
+ /afs/athena.mit.edu/contrib/scripts/deploydev/updates/update-scripts-version.pl
+ """
+ def __init__(self):
+ pass # prevent not implemented error
+
+class WizardUpgrade(DeploySource):
+ """Upgrade using wizard infrastructure, characterized by
+ /afs/athena.mit.edu/contrib/scripts/wizard/bin/wizard HASHGOBBLEDYGOOK
+ """
+ def __init__(self, rev):
+ self.rev = rev
+
+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):
+ """ `version` Instance of distutils.LooseVersion
+ `application` Instance of Application"""
self.version = version
self.application = application
self.c = 0
self.c_exists = {}
def __cmp__(x, y):
return cmp(x.version, y.version)
+ @staticmethod
+ def parse(line):
+ # The version of the deployment, will be:
+ # /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
+ # /afs/athena.mit.edu/contrib/scripts/wizard/srv/APP.git vx.y.z-scripts for new style installs
+ raise NotImplemented
+ # This is summary specific code
def count(self, deployment):
self.c += 1
self.application._total += 1