4 import distutils.version
10 ## -- Global Functions --
12 def getInstallLines(vs):
13 """Retrieves a list of lines from the version directory that
14 can be passed to Deployment.parse()"""
15 if os.path.isfile(vs):
16 return fileinput.input([vs])
17 return fileinput.input([vs + "/" + f for f in os.listdir(vs)])
19 def parse_install_lines(show, options, yield_errors = False):
20 if not show: show = applications()
21 show = frozenset(show)
22 for line in getInstallLines(options.versions_path):
25 d = Deployment.parse(line)
26 name = d.application.name
27 except NoSuchApplication as e:
32 # we consider this a worse error
33 logging.warning("Error with '%s'" % line.rstrip())
36 if name + "-" + str(d.version) in show or name in show:
43 ## -- Model Objects --
45 class Deployment(object):
46 """Represents a deployment of an autoinstall; i.e. a concrete
47 directory in web_scripts that has .scripts-version in it."""
48 def __init__(self, location, version=None):
49 """ `location` Location of the deployment
50 `version` ApplicationVersion of the app. ONLY supply this
51 if you don't mind having stale data; generally
52 'wizard list' and commands that operate of of the
53 versions store will set this."""
54 self.location = os.path.realpath(location)
55 self._app_version = version
56 # some cache variables
59 def read(self, file, force = False):
60 """Reads a file's contents, possibly from cache unless force is True."""
61 if force or file not in self._read_cache:
62 f = open(os.path.join(self.location, file))
63 self._read_cache[file] = f.read()
65 return self._read_cache[file]
67 """Extracts all the values of all variables from deployment."""
68 return self.application.extract(self)
69 def parametrize(self, dir):
70 """Edits files in dir to replace WIZARD_* variables with literal
71 instances. This is used for constructing virtual merge bases, and
72 as such dir will generally not equal self.location."""
73 return self.application.parametrize(self, dir)
74 def updateVersion(self, version):
75 """Bump the version of this deployment.
77 This method will update the version of this deployment in memory
78 and on disk. It doesn't actually do an upgrade. The version
79 string you pass here should probably have '-scripts' as a suffix."""
80 self._app_version = self.application.makeVersion(version)
81 f = open(os.path.join(self.scripts_dir, 'version'), 'w')
82 f.write(self.application.name + '-' + version + "\n")
84 def scriptsifyVersion(self):
85 """Converts from v1.0 to v1.0-scripts; use at end of migration."""
86 self.updateVersion(str(self.version) + '-scripts')
88 def scripts_dir(self):
89 return os.path.join(self.location, '.scripts')
91 def old_version_file(self):
92 """Use of this is discouraged for migrated installs."""
93 if os.path.isdir(self.scripts_dir):
94 return os.path.join(self.scripts_dir, 'old-version')
96 return os.path.join(self.location, '.scripts-version')
98 def version_file(self):
99 return os.path.join(self.scripts_dir, 'version')
101 def application(self):
102 return self.app_version.application
106 self._log = log.DeployLog.load(self)
110 """Returns the distutils Version of the deployment"""
111 return self.app_version.version
113 def app_version(self):
114 """Returns the ApplicationVersion of the deployment"""
115 if not self._app_version:
116 if os.path.isfile(self.version_file):
117 fh = open(self.version_file)
118 appname, _, version = fh.read().rstrip().partition('-')
120 self._app_version = ApplicationVersion.make(appname, version)
122 self._app_version = self.log[-1].version
123 return self._app_version
126 """Parses a line from the versions directory.
128 Note: Use this method only when speed is of the utmost
129 importance. You should prefer to directly create a deployment
130 using Deployment(location) when accuracy is desired."""
133 location, deploydir = line.split(":")
135 return Deployment(line) # lazy loaded version
137 return Deployment(location, version=ApplicationVersion.parse(deploydir))
139 e.location = location
142 class Application(object):
143 """Represents the generic notion of an application, i.e.
144 mediawiki or phpbb."""
145 parametrized_files = []
146 def __init__(self, name):
150 self._extractors = {}
151 self._parametrizers = {}
153 def repository(self):
154 """Returns the Git repository that would contain this application."""
155 repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/git/autoinstalls", self.name + ".git")
156 if not os.path.isdir(repo):
157 raise NoRepositoryError(app)
159 def makeVersion(self, version):
160 if version not in self.versions:
161 self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
162 return self.versions[version]
163 def extract(self, deployment):
164 """Extracts wizard variables from a deployment."""
166 for k,extractor in self.extractors.items():
167 result[k] = extractor(deployment)
169 def parametrize(self, deployment, dir):
170 """Takes a generic source checkout at dir and parametrizes
171 it according to the values of deployment."""
172 variables = deployment.extract()
173 for file in self.parametrized_files:
174 fullpath = os.path.join(dir, file)
175 f = open(fullpath, "r")
178 for key, value in variables.items():
179 if value is None: continue
180 contents = contents.replace(key, value)
181 tmp = tempfile.NamedTemporaryFile(delete=False)
184 os.rename(tmp.name, fullpath)
186 def extractors(self):
190 """Makes an application, but uses the correct subtype if available."""
192 __import__("wizard.app." + name)
193 return getattr(wizard.app, name).Application(name)
195 return Application(name)
197 class ApplicationVersion(object):
198 """Represents an abstract notion of a version for an application"""
199 def __init__(self, version, application):
200 """ `version` Instance of distutils.LooseVersion
201 `application` Instance of Application
202 WARNING: Please don't call this directly; instead, use getVersion()
203 on the application you want, so that this version gets registered."""
204 self.version = version
205 self.application = application
207 def scripts_tag(self):
208 """Returns the name of the Git tag for this version"""
209 # XXX: This assumes that there's only a -scripts version
210 # which will not be true in the future. Unfortunately, finding
211 # the "true" latest version is computationally expensive
212 return "v%s-scripts" % self.version
214 return cmp(x.version, y.version)
217 """Parses a line from the versions directory and return ApplicationVersion.
219 Use this only for cases when speed is of primary importance;
220 the data in version is unreliable and when possible, you should
221 prefer directly instantiating a Deployment and having it query
222 the autoinstall itself for information.
224 value : The value to parse, will look like:
225 /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
226 APP-x.y.z-scripts for wizard style installs
228 name = value.split("/")[-1]
230 if name.find("-") != -1:
231 app, _, version = name.partition("-")
233 # kind of poor, maybe should error. Generally this
234 # will actually result in a not found error
238 raise DeploymentParseError(deploydir)
239 return ApplicationVersion.make(app, version)
241 def make(app, version):
243 # defer to the application for version creation to enforce
245 return applications()[app].makeVersion(version)
247 raise NoSuchApplication(app)
251 class Error(wizard.Error):
252 """Base error class for this module"""
255 class NoSuchApplication(Error):
256 def __init__(self, app):
257 """app : Application that doesn't exist"""
259 self.location = None # filled in when available
261 class DeploymentParseError(Error):
262 def __init__(self, value):
263 """value : Value from 'versions' that could not be parsed"""
265 self.location = None # filled in when available
267 class NoRepositoryError(Error):
268 def __init__(self, app):
269 """app : The application that doesn't have a Git repository"""
272 # If you want, you can wrap this up into a registry and access things
273 # through that, but it's not really necessary
276 "mediawiki", "wordpress", "joomla", "e107", "gallery2",
277 "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
278 # these are technically deprecated
279 "advancedpoll", "gallery",
284 """Hash table for looking up string application name to instance"""
286 if not _applications:
287 _applications = dict([(n,Application.make(n)) for n in application_list ])