5 from distutils.version import LooseVersion as Version
7 def getInstallLines(global_options):
8 """Retrieves a list of lines from the version directory that
9 can be passed to Deployment.parse()"""
10 vd = global_options.version_dir
12 return fileinput.input([vd + "/" + f for f in os.listdir(vd)])
14 print "No permissions; check if AFS is mounted"
17 class NoSuchApplication(Exception):
20 class DeploymentParseError(Exception):
23 class Deployment(object):
24 """Represents a deployment of an autoinstall; i.e. a concrete
25 directory in web_scripts that has .scripts-version in it."""
26 def __init__(self, location, log=None, version=None):
27 """ `location` Location of the deployment
28 `version` ApplicationVersion of the app (this is cached info)
29 `log` DeployLog of the app"""
30 # XXX: This constructor should change
31 self.location = location
32 self._version = version
34 # Maybe should be an accessor
35 self.application = version.application
38 """Parses a line from the results of parallel-find.pl.
39 This will work out of the box with fileinput, see
42 location, deploydir = line.rstrip().split(":")
44 raise DeploymentParseError
45 name = deploydir.split("/")[-1]
46 if name.find("-") != -1:
47 app, version = name.split("-")
48 elif name == "deploy":
49 # Assume that it's django, since those were botched
51 version = "0.1-scripts"
53 raise DeploymentParseError
55 return Deployment(location, version=applications[app].getVersion(version))
57 raise NoSuchApplication
60 """Creates a deployment from a directory"""
61 version_file = os.path.join(dir, '.scripts-version')
62 # needs deployment log
65 # XXX: Load the deployment log
69 """Returns the distutils Version of the deployment"""
70 return self.getAppVersion().version
71 def getAppVersion(self, force = False):
72 """Returns the ApplicationVersion of the deployment"""
73 if self._version and not force: return self._version
74 else: return self.getLog()[-1].version
75 # XXX: This is summary specific code
77 """Simple method which registers the deployment as a +1 on the
78 appropriate version. No further inspection is done."""
79 self.getAppVersion().count(self)
81 def count_exists(self, file):
82 """Checks if the codebase has a certain file/directory in it."""
83 if os.path.exists(self.location + "/" + file):
84 self.getAppVersion().count_exists(self, file)
88 class Application(object):
89 """Represents the generic notion of an application, i.e.
90 mediawiki or phpbb."""
93 def __init__(self, name):
96 # Some cache variables for fast access of calculated data
100 def getVersion(self, version):
101 if version not in self.versions:
102 self.versions[version] = ApplicationVersion(Version(version), self)
103 return self.versions[version]
104 # XXX: This code should go in summary.py; maybe as a mixin, maybe as
107 return '+' * int(math.ceil(float(v)/self._max * self.HISTOGRAM_WIDTH))
109 if not self.versions: return "%-11s no installs" % self.name
111 ["%-16s %3d installs" % (self.name, self._total)] + \
112 [str(v) for v in sorted(self.versions.values())]
113 for f,c in self._c_exists.items():
114 ret.append("%d users have %s" % (c,f))
115 return "\n".join(ret)
117 class DeployLog(list):
118 # As per #python; if you decide to start overloading magic methods,
119 # we should remove this subclass
120 """Equivalent to .scripts-version: a series of DeployRevisions."""
121 def __init__(self, revs = []):
122 """`revs` List of DeployRevision objects"""
124 list.__init__(self, revs)
127 """Loads a scripts version file and parses it into
128 DeployLog and DeployRevision objects"""
130 rev = DeployRevision()
132 for line in open(file):
137 rev = DeployRevision()
140 rev.datetime = dateutil.parser.parse(line)
144 rev.source = DeploySource.parse(line)
146 rev.version = ApplicationVersion.parse(line)
151 if i: revs.append(rev)
152 return DeployLog(revs)
154 return '<DeployLog %s>' % list.__repr__(self)
156 class DeployRevision(object):
157 """A single entry in the .scripts-version file. Contains who deployed
158 this revision, what application version this is, etc."""
159 def __init__(self, datetime=None, user=None, source=None, version=None):
160 """ `datetime` Time this revision was deployed
161 `user` Person who deployed this revision, in user@host format.
162 `source` Instance of DeploySource
163 `version` Instance of ApplicationVersion
164 Note: This object is typically built incrementally."""
165 self.datetime = datetime
168 self.version = version
170 class DeploySource(object):
171 """Source of the deployment; see subclasses for examples"""
173 raise NotImplemented # abstract class
178 class TarballInstall(DeploySource):
179 """Original installation from tarball, characterized by
180 /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z.tar.gz
182 def __init__(self, location):
183 self.location = location
185 class OldUpgrade(DeploySource):
186 """Upgrade using old upgrade infrastructure, characterized by
187 /afs/athena.mit.edu/contrib/scripts/deploydev/updates/update-scripts-version.pl
190 pass # prevent not implemented error
192 class WizardUpgrade(DeploySource):
193 """Upgrade using wizard infrastructure, characterized by
194 /afs/athena.mit.edu/contrib/scripts/wizard/bin/wizard HASHGOBBLEDYGOOK
196 def __init__(self, rev):
199 class UnknownDeploySource(DeploySource):
200 """Deployment that we don't know the meaning of. Wot!"""
201 def __init__(self, line):
204 class ApplicationVersion(object):
205 """Represents an abstract notion of a version for an application"""
206 def __init__(self, version, application):
207 """ `version` Instance of distutils.LooseVersion
208 `application` Instance of Application"""
209 self.version = version
210 self.application = application
214 return cmp(x.version, y.version)
217 # The version of the deployment, will be:
218 # /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
219 # /afs/athena.mit.edu/contrib/scripts/wizard/srv/APP.git vx.y.z-scripts for new style installs
221 # This is summary specific code
222 def count(self, deployment):
224 self.application._total += 1
225 if self.c > self.application._max:
226 self.application._max = self.c
227 def count_exists(self, deployment, n):
228 if n in self.c_exists: self.c_exists[n] += 1
229 else: self.c_exists[n] = 1
230 if n in self.application._c_exists: self.application._c_exists[n] += 1
231 else: self.application._c_exists[n] = 1
233 return " %-12s %3d %s" \
234 % (self.version, self.c, self.application._graph(self.c))
237 "mediawiki", "wordpress", "joomla", "e107", "gallery2",
238 "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
239 # these are technically deprecated
240 "advancedpoll", "gallery",
243 """Hash table for looking up string application name to instance"""
244 applications = dict([(n,Application(n)) for n in application_list ])