]> scripts.mit.edu Git - wizard.git/blob - wizard/deploy.py
Add mediawiki variable extraction support for deployments.
[wizard.git] / wizard / deploy.py
1 import os.path
2 import fileinput
3 import dateutil.parser
4 import distutils.version
5
6 import wizard
7 from wizard import log
8
9 ## -- Global Functions --
10
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)])
17
18 ## -- Model Objects --
19
20 class Deployment(object):
21     """Represents a deployment of an autoinstall; i.e. a concrete
22     directory in web_scripts that has .scripts-version in it."""
23     def __init__(self, location, log=None, version=None):
24         """ `location`  Location of the deployment
25             `version`   ApplicationVersion of the app (this is cached info)
26             `log`       DeployLog of the app"""
27         self.location = location
28         self._version = version
29         self._log = log
30         self._read_cache = {}
31     def read(self, file, force = False):
32         """Reads a file's contents and stuffs it in a cache"""
33         if force or file not in self._read_cache:
34             f = open(os.path.join(self.location, file))
35             self._read_cache[file] = f.read()
36             f.close()
37         return self._read_cache[file]
38     @property
39     def version_file(self):
40         return os.path.join(self.location, '.scripts-version')
41     @property
42     def application(self):
43         return self.app_version.application
44     @property
45     def log(self):
46         if not self._log:
47             self._log = log.DeployLog.load(self.version_file)
48         return self._log
49     @property
50     def version(self):
51         """Returns the distutils Version of the deployment"""
52         return self.app_version.version
53     @property
54     def app_version(self, force = False):
55         """Returns the ApplicationVersion of the deployment"""
56         if self._version and not force: return self._version
57         else: return self.log[-1].version
58     @staticmethod
59     def parse(line):
60         """Parses a line from the results of parallel-find.pl.
61         This will work out of the box with fileinput, see
62         getInstallLines()"""
63         line = line.rstrip()
64         try:
65             location, deploydir = line.split(":")
66         except ValueError:
67             return Deployment(line) # lazy loaded version
68         return Deployment(location, version=ApplicationVersion.parse(deploydir, location))
69
70 class Application(object):
71     """Represents the generic notion of an application, i.e.
72     mediawiki or phpbb."""
73     def __init__(self, name):
74         self.name = name
75         self.versions = {}
76         self._extractors = {}
77     @property
78     def repository(self):
79         """Returns the Git repository that would contain this application."""
80         repo = os.path.join("/afs/athena.mit.edu/contrib/scripts/git/autoinstalls", self.name + ".git")
81         if not os.path.isdir(repo):
82             raise NoRepositoryError(app)
83         return repo
84     def makeVersion(self, version):
85         if version not in self.versions:
86             self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
87         return self.versions[version]
88     def extract(self, deployment):
89         """Extracts wizard variables from a deployment."""
90         result = {}
91         for k,extractor in self.extractors.items():
92             result[k] = extractor(deployment)
93         return result
94     @property
95     def extractors(self):
96         return {}
97     @staticmethod
98     def make(name):
99         """Makes an application, but uses the correct subtype if available."""
100         try:
101             __import__("wizard.app." + name)
102             return getattr(wizard.app, name).Application(name)
103         except ImportError:
104             return Application(name)
105
106 class ApplicationVersion(object):
107     """Represents an abstract notion of a version for an application"""
108     def __init__(self, version, application):
109         """ `version`       Instance of distutils.LooseVersion
110             `application`   Instance of Application
111         WARNING: Please don't call this directly; instead, use getVersion()
112         on the application you want, so that this version gets registered."""
113         self.version = version
114         self.application = application
115     @property
116     def scripts_tag(self):
117         """Returns the name of the Git tag for this version"""
118         # XXX: This assumes that there's only a -scripts version
119         # which will not be true in the future.  Unfortunately, finding
120         # the "true" latest version is computationally expensive
121         return "v%s-scripts" % self.version
122     def __cmp__(x, y):
123         return cmp(x.version, y.version)
124     @staticmethod
125     def parse(deploydir,location,applookup=None):
126         # The version of the deployment, will be:
127         #   /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z for old style installs
128         name = deploydir.split("/")[-1]
129         try:
130             if name.find(" ") != -1:
131                 raw_app, raw_version = name.split(" ")
132                 version = raw_version[1:] # remove leading v
133                 app, _ = raw_app.split(".") # remove trailing .git
134             elif name.find("-") != -1:
135                 app, _, version = name.partition("-")
136             else:
137                 app = name
138                 version = "trunk"
139         except ValueError: # mostly from the a, b = foo.split(' ')
140             raise DeploymentParseError(deploydir, location)
141         if not applookup: applookup = applications()
142         try:
143             # defer to the application for version creation
144             return applookup[app].makeVersion(version)
145         except KeyError:
146             raise NoSuchApplication(app, location)
147
148 ## -- Exceptions --
149
150 class Error(Exception):
151     """Base error class for this module"""
152     pass
153
154 class NoSuchApplication(Error):
155     def __init__(self, name, location):
156         self.name = name
157         self.location = location
158     def __str__(self):
159         return "ERROR: Unrecognized app '%s' at %s" % (self.name, self.location)
160
161 class DeploymentParseError(Error):
162     def __init__(self, malformed, location):
163         self.malformed = malformed
164         self.location = location
165     def __str__(self):
166         return """ERROR: Unparseable '%s' at %s""" % (self.malformed, self.location)
167
168 class NoRepositoryError(Error):
169     def __init__(self, app):
170         self.app = app
171         self.location = "unknown"
172     def __str__(self):
173         return """
174
175 ERROR: Could not find repository for this application. Have
176 you converted the repository over? Is the name %s
177 the same as the name of the .git folder?
178 """ % self.app
179
180 # If you want, you can wrap this up into a registry and access things
181 # through that, but it's not really necessary
182
183 application_list = [
184     "mediawiki", "wordpress", "joomla", "e107", "gallery2",
185     "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
186     # these are technically deprecated
187     "advancedpoll", "gallery",
188 ]
189 _applications = None
190
191 def applications():
192     """Hash table for looking up string application name to instance"""
193     global _applications
194     if not _applications:
195         _applications = dict([(n,Application.make(n)) for n in application_list ])
196     return _applications
197