4 import distutils.version
9 ## -- Global Functions --
11 def getInstallLines(vs):
12 """Retrieves a list of lines from the version directory that
13 can be passed to Deployment.parse()"""
14 if os.path.isfile(vs):
15 return fileinput.input([vs])
16 return fileinput.input([vs + "/" + f for f in os.listdir(vs)])
18 def parse_install_lines(show, options, yield_errors = False):
19 if not show: show = applications()
20 show = frozenset(show)
21 for line in getInstallLines(options.versions_path):
24 d = Deployment.parse(line)
25 name = d.application.name
26 except NoSuchApplication as e:
31 # we consider this a worse error
32 logging.warning("Error with '%s'" % line.rstrip())
35 if name + "-" + str(d.version) in show or name in show:
42 ## -- Model Objects --
44 class Deployment(object):
45 """Represents a deployment of an autoinstall; i.e. a concrete
46 directory in web_scripts that has .scripts-version in it."""
47 def __init__(self, location, version=None):
48 """ `location` Location of the deployment
49 `version` ApplicationVersion of the app. ONLY supply this
50 if you don't mind having stale data; generally
51 'wizard list' and commands that operate of of the
52 versions store will set this."""
53 self.location = location
54 self._app_version = version
55 # some cache variables
58 def read(self, file, force = False):
59 """Reads a file's contents, possibly from cache unless force is True."""
60 if force or file not in self._read_cache:
61 f = open(os.path.join(self.location, file))
62 self._read_cache[file] = f.read()
64 return self._read_cache[file]
66 """Extracts all the values of all variables from deployment."""
67 return self.application.extract(self)
68 def updateVersion(self, version):
69 """Bump the version of this deployment.
71 This method will update the version of this deployment in memory
72 and on disk. It doesn't actually do an upgrade. The version
73 string you pass here should probably have '-scripts' as a suffix."""
74 self._app_version = self.application.makeVersion(version)
75 f = open(os.path.join(self.scripts_dir, 'version'), 'w')
76 f.write(self.application.name + '-' + version + "\n")
78 def scriptsifyVersion(self):
79 """Converts from v1.0 to v1.0-scripts; use at end of migration."""
80 self.updateVersion(str(self.version) + '-scripts')
82 def scripts_dir(self):
83 return os.path.join(self.location, '.scripts')
85 def old_version_file(self):
86 """Use of this is discouraged for migrated installs."""
87 if os.path.isdir(self.scripts_dir):
88 return os.path.join(self.scripts_dir, 'old-version')
90 return os.path.join(self.location, '.scripts-version')
92 def version_file(self):
93 return os.path.join(self.scripts_dir, 'version')
95 def application(self):
96 return self.app_version.application
100 self._log = log.DeployLog.load(self)
104 """Returns the distutils Version of the deployment"""
105 return self.app_version.version
107 def app_version(self):
108 """Returns the ApplicationVersion of the deployment"""
109 if not self._app_version:
110 if os.path.isfile(self.version_file):
111 fh = open(self.version_file)
112 appname, _, version = fh.read().rstrip().partition('-')
114 self._app_version = ApplicationVersion.make(appname, version)
116 self._app_version = self.log[-1].version
117 return self._app_version
120 """Parses a line from the versions directory.
122 Note: Use this method only when speed is of the utmost
123 importance. You should prefer to directly create a deployment
124 using Deployment(location) when accuracy is desired."""
127 location, deploydir = line.split(":")
129 return Deployment(line) # lazy loaded version
131 return Deployment(location, version=ApplicationVersion.parse(deploydir))
133 e.location = location
136 class Application(object):
137 """Represents the generic notion of an application, i.e.
138 mediawiki or phpbb."""
139 def __init__(self, name):
143 self._extractors = {}
145 def repository(self):
146 """Returns the Git repository that would contain this application."""
147 repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/git/autoinstalls", self.name + ".git")
148 if not os.path.isdir(repo):
149 raise NoRepositoryError(app)
151 def makeVersion(self, version):
152 if version not in self.versions:
153 self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
154 return self.versions[version]
155 def extract(self, deployment):
156 """Extracts wizard variables from a deployment."""
158 for k,extractor in self.extractors.items():
159 result[k] = extractor(deployment)
162 def extractors(self):
166 """Makes an application, but uses the correct subtype if available."""
168 __import__("wizard.app." + name)
169 return getattr(wizard.app, name).Application(name)
171 return Application(name)
173 class ApplicationVersion(object):
174 """Represents an abstract notion of a version for an application"""
175 def __init__(self, version, application):
176 """ `version` Instance of distutils.LooseVersion
177 `application` Instance of Application
178 WARNING: Please don't call this directly; instead, use getVersion()
179 on the application you want, so that this version gets registered."""
180 self.version = version
181 self.application = application
183 def scripts_tag(self):
184 """Returns the name of the Git tag for this version"""
185 # XXX: This assumes that there's only a -scripts version
186 # which will not be true in the future. Unfortunately, finding
187 # the "true" latest version is computationally expensive
188 return "v%s-scripts" % self.version
190 return cmp(x.version, y.version)
193 """Parses a line from the versions directory and return ApplicationVersion.
195 Use this only for cases when speed is of primary importance;
196 the data in version is unreliable and when possible, you should
197 prefer directly instantiating a Deployment and having it query
198 the autoinstall itself for information.
200 value : The value to parse, will look like:
201 /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
202 APP-x.y.z-scripts for wizard style installs
204 name = value.split("/")[-1]
206 if name.find("-") != -1:
207 app, _, version = name.partition("-")
209 # kind of poor, maybe should error. Generally this
210 # will actually result in a not found error
214 raise DeploymentParseError(deploydir)
215 return ApplicationVersion.make(app, version)
217 def make(app, version):
219 # defer to the application for version creation to enforce
221 return applications()[app].makeVersion(version)
223 raise NoSuchApplication(app)
227 class Error(Exception):
228 """Base error class for this module"""
231 class NoSuchApplication(Error):
232 def __init__(self, app):
233 """app : Application that doesn't exist"""
235 self.location = None # filled in when available
237 class DeploymentParseError(Error):
238 def __init__(self, value):
239 """value : Value from 'versions' that could not be parsed"""
241 self.location = None # filled in when available
243 class NoRepositoryError(Error):
244 def __init__(self, app):
245 """app : The application that doesn't have a Git repository"""
248 # If you want, you can wrap this up into a registry and access things
249 # through that, but it's not really necessary
252 "mediawiki", "wordpress", "joomla", "e107", "gallery2",
253 "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
254 # these are technically deprecated
255 "advancedpoll", "gallery",
260 """Hash table for looking up string application name to instance"""
262 if not _applications:
263 _applications = dict([(n,Application.make(n)) for n in application_list ])