]> scripts.mit.edu Git - wizard.git/blob - wizard/log.py
Refactor code to make regular expression reuse easier.
[wizard.git] / wizard / log.py
1 import os.path
2 import dateutil.parser
3
4 import wizard.deploy # otherwise circular dep is unhappy
5
6 # This code operates off of the assumption of .scripts-version, which
7 # is not true.
8
9 class DeployLog(list):
10     # As per #python; if you decide to start overloading magic methods,
11     # we should remove this subclass
12     """Equivalent to .scripts-version: a series of DeployRevisions."""
13     def __init__(self, revs = []):
14         """`revs`  List of DeployRevision objects"""
15         list.__init__(self, revs) # pass to list
16     def __repr__(self):
17         return '<DeployLog %s>' % list.__repr__(self)
18     @staticmethod
19     def load(file):
20         """Loads a scripts version file and parses it into
21         DeployLog and DeployRevision objects"""
22         # XXX: DIRTY DIRTY HACK
23         # What we should actually do is parse the git logs
24         scriptsdir = os.path.join(os.path.dirname(file), ".scripts")
25         if os.path.isdir(scriptsdir):
26             file = os.path.join(scriptsdir, "old-version")
27         i = 0
28         rev = DeployRevision()
29         revs = []
30         def append(rev):
31             if i:
32                 if i != 4:
33                     raise ScriptsVersionNotEnoughFieldsError(file)
34                 revs.append(rev)
35         try:
36             fh = open(file)
37         except IOError:
38             raise ScriptsVersionNoSuchFile(file)
39         for line in fh:
40             line = line.rstrip()
41             if not line:
42                 append(rev)
43                 i = 0
44                 rev = DeployRevision()
45                 continue
46             if i == 0:
47                 # we need the dateutil parser in order to
48                 # be able to parse time offsets
49                 rev.datetime = dateutil.parser.parse(line)
50             elif i == 1:
51                 rev.user = line
52             elif i == 2:
53                 rev.source = DeploySource.parse(line)
54             elif i == 3:
55                 rev.version = wizard.deploy.ApplicationVersion.parse(line, rev.source)
56             else:
57                 # ruh oh
58                 raise ScriptsVersionTooManyFieldsError(file)
59             i += 1
60         append(rev)
61         return DeployLog(revs)
62
63 class DeployRevision(object):
64     """A single entry in the .scripts-version file. Contains who deployed
65     this revision, what application version this is, etc."""
66     def __init__(self, datetime=None, user=None, source=None, version=None):
67         """ `datetime`  Time this revision was deployed
68             `user`      Person who deployed this revision, in user@host format.
69             `source`    Instance of DeploySource
70             `version`   Instance of ApplicationVersion
71         Note: This object is typically built incrementally."""
72         self.datetime = datetime
73         self.user = user
74         self.source = source
75         self.version = version
76
77 class DeploySource(object):
78     """Source of the deployment; see subclasses for examples"""
79     def __init__(self):
80         raise NotImplementedError # abstract class
81     @staticmethod
82     def parse(line):
83         # munge out common prefix
84         rel = os.path.relpath(line, "/afs/athena.mit.edu/contrib/scripts/")
85         parts = rel.split("/")
86         if parts[0] == "wizard":
87             return WizardUpdate()
88         elif parts[0] == "deploy" or parts[0] == "deploydev":
89             isDev = ( parts[0] == "deploydev" )
90             try:
91                 if parts[1] == "updates":
92                     return OldUpdate(isDev)
93                 else:
94                     return TarballInstall(line, isDev)
95             except IndexError:
96                 pass
97         return UnknownDeploySource(line)
98
99 class TarballInstall(DeploySource):
100     """Original installation from tarball, characterized by
101     /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z.tar.gz
102     """
103     def __init__(self, location, isDev):
104         self.location = location
105         self.isDev = isDev
106
107 class OldUpdate(DeploySource):
108     """Upgrade using old upgrade infrastructure, characterized by
109     /afs/athena.mit.edu/contrib/scripts/deploydev/updates/update-scripts-version.pl
110     """
111     def __init__(self, isDev):
112         self.isDev = isDev
113
114 class WizardUpdate(DeploySource):
115     """Upgrade using wizard infrastructure, characterized by
116     /afs/athena.mit.edu/contrib/scripts/wizard/bin/wizard HASHGOBBLEDYGOOK
117     """
118     def __init__(self):
119         pass
120
121 class UnknownDeploySource(DeploySource):
122     """Deployment that we don't know the meaning of. Wot!"""
123     def __init__(self, line):
124         self.line = line
125
126 ## -- Exceptions --
127
128 class Error(Exception):
129     """Base error class for log errors."""
130     pass
131
132 class ScriptsVersionError(Error):
133     """Errors specific to the parsing of a full .scripts-version file
134     (errors that could also be triggered while parsing a parallel-find
135     output should not be this subclass.)"""
136     pass
137
138 class ScriptsVersionTooManyFieldsError(ScriptsVersionError):
139     def __str__(self):
140         return """
141
142 ERROR: Could not parse .scripts-version file.  It
143 contained too many fields.
144 """
145
146 class ScriptsVersionNotEnoughFieldsError(ScriptsVersionError):
147     def __str__(self):
148         return """
149
150 ERROR: Could not parse .scripts-version file. It
151 didn't contain enough fields.
152 """
153
154 class ScriptsVersionNoSuchFile(ScriptsVersionError):
155     def __init__(self, file):
156         self.file = file
157     def __str__(self):
158         return """
159
160 ERROR: File %s didn't exist.
161 """ % self.file
162