145.1.1
by Abel Deuring
more copyright notcies added. |
1 |
# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
4 |
"""Migrate data for the mongodb instance.
|
5 |
||
6 |
Loads migrations from the latest available in the versions directory.
|
|
7 |
||
8 |
"""
|
|
100.1.6
by Rick Harding
working on cli for migrate |
9 |
import argparse |
100.1.16
by Rick Harding
Update per code review |
10 |
from datetime import datetime |
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
11 |
import imp |
12 |
from os import listdir |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
13 |
from os.path import abspath |
100.1.6
by Rick Harding
working on cli for migrate |
14 |
from os.path import dirname |
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
15 |
from os.path import join |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
16 |
import re |
17 |
||
128.1.1
by Rick Harding
Big chunk of reworking db |
18 |
from charmworld.models import getconnection |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
19 |
from charmworld.models import getdb |
100.1.6
by Rick Harding
working on cli for migrate |
20 |
from charmworld.utils import get_ini |
21 |
||
100.1.16
by Rick Harding
Update per code review |
22 |
SCRIPT_TEMPLATE = """ |
100.1.3
by Rick Harding
Update to create a new version file |
23 |
# {description} |
24 |
||
25 |
def upgrade(db):
|
|
100.1.5
by Rick Harding
Lint |
26 |
\"\"\"Complete this function with work to be done for the migration/update. |
100.1.3
by Rick Harding
Update to create a new version file |
27 |
|
28 |
db is the pymongo db instance for our datastore. Charms are in db.charms
|
|
29 |
for instance.
|
|
30 |
\"\"\" |
|
31 |
||
32 |
||
33 |
"""
|
|
34 |
||
35 |
||
36 |
def str_to_filename(s): |
|
37 |
"""Replaces spaces, quotes, and double underscores to underscores."""
|
|
100.1.5
by Rick Harding
Lint |
38 |
s = s.replace(' ', '_')\ |
39 |
.replace('"', '_')\ |
|
40 |
.replace("'", '_')\ |
|
41 |
.replace(".", "_") |
|
100.1.3
by Rick Harding
Update to create a new version file |
42 |
while '__' in s: |
43 |
s = s.replace('__', '_') |
|
44 |
return s |
|
45 |
||
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
46 |
|
47 |
class DataStore(object): |
|
48 |
"""Communicate with the data store to determine version status."""
|
|
49 |
||
50 |
def __init__(self, db): |
|
51 |
"""Talk to the data store
|
|
52 |
||
53 |
:param db: The mongo db connection.
|
|
54 |
||
55 |
"""
|
|
56 |
self.db = db |
|
57 |
||
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
58 |
@property
|
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
59 |
def current_version(self): |
60 |
"""Return the current version of the data store."""
|
|
89.2.15
by Rick Harding
Merge with trunk, update to use migration, change naming of questions |
61 |
found = self.db.migration_version.find_one({ |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
62 |
'_id': 'version' |
63 |
})
|
|
64 |
||
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
65 |
if found: |
66 |
return found['version'] |
|
67 |
else: |
|
68 |
return None |
|
69 |
||
70 |
def update_version(self, version): |
|
71 |
"""Update the version number in the data store."""
|
|
89.2.15
by Rick Harding
Merge with trunk, update to use migration, change naming of questions |
72 |
self.db.migration_version.save({ |
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
73 |
'_id': 'version', |
100.1.16
by Rick Harding
Update per code review |
74 |
'version': version, |
75 |
'date': datetime.now(), |
|
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
76 |
})
|
77 |
||
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
78 |
def version_datastore(self): |
79 |
"""Init the data to track the current version in the datastore.
|
|
80 |
||
81 |
The data store starts out with version 0.
|
|
82 |
||
83 |
Raises exception if the store is already versioned.
|
|
84 |
"""
|
|
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
85 |
if self.current_version is not None: |
86 |
raise Exception('Data store is already versioned: {0}'.format( |
|
87 |
self.current_version)) |
|
89.2.15
by Rick Harding
Merge with trunk, update to use migration, change naming of questions |
88 |
self.db.migration_version.insert({ |
100.1.16
by Rick Harding
Update per code review |
89 |
'_id': 'version', |
90 |
'version': 0, |
|
91 |
'date': datetime.now(), |
|
92 |
})
|
|
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
93 |
|
94 |
||
95 |
class Versions(object): |
|
96 |
||
97 |
def __init__(self, path): |
|
98 |
"""Collect version scripts and store them in self.versions
|
|
99 |
||
100 |
"""
|
|
100.1.16
by Rick Harding
Update per code review |
101 |
FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*') |
100.1.3
by Rick Harding
Update to create a new version file |
102 |
self.versions_dir = path |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
103 |
# Create temporary list of files, allowing skipped version numbers.
|
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
104 |
files = listdir(path) |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
105 |
versions = {} |
106 |
||
107 |
for name in files: |
|
89.2.16
by Rick Harding
Fix the naming to not use a dot since that's mongo notation doh |
108 |
if not name.endswith('.py'): |
109 |
# Skip .pyc and the like
|
|
110 |
continue
|
|
111 |
||
100.1.16
by Rick Harding
Update per code review |
112 |
match = FILENAME_WITH_VERSION.match(name) |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
113 |
if match: |
114 |
num = int(match.group(1)) |
|
115 |
versions[num] = name |
|
116 |
else: |
|
117 |
pass # Must be a helper file or something, let's ignore it. |
|
118 |
||
119 |
self.versions = {} |
|
120 |
version_numbers = versions.keys() |
|
121 |
version_numbers.sort() |
|
122 |
||
123 |
self.version_indexes = version_numbers |
|
124 |
for idx in version_numbers: |
|
125 |
self.versions[idx] = versions[idx] |
|
126 |
||
100.1.3
by Rick Harding
Update to create a new version file |
127 |
def create_new_version_file(self, description): |
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
128 |
"""Create Python files for new version"""
|
100.1.3
by Rick Harding
Update to create a new version file |
129 |
version = self.latest + 1 |
130 |
||
131 |
if not description: |
|
132 |
raise ValueError('Please provide a short migration description.') |
|
133 |
||
134 |
filename = "{0}_{1}.py".format( |
|
135 |
str(version).zfill(3), |
|
136 |
str_to_filename(description) |
|
137 |
)
|
|
138 |
||
139 |
filepath = join(self.versions_dir, filename) |
|
100.1.16
by Rick Harding
Update per code review |
140 |
with open(filepath, 'w') as new_migration: |
141 |
new_migration.write( |
|
142 |
SCRIPT_TEMPLATE.format(description=description)) |
|
100.1.3
by Rick Harding
Update to create a new version file |
143 |
|
144 |
# Update our current store of version data.
|
|
145 |
self.versions[version] = filename |
|
146 |
self.version_indexes.append(version) |
|
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
147 |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
148 |
return filename |
149 |
||
100.1.1
by Rick Harding
Initial migrations code with a couple passing tests |
150 |
@property
|
151 |
def latest(self): |
|
152 |
""":returns: Latest version in Collection"""
|
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
153 |
return self.version_indexes[-1] if len(self.version_indexes) > 0 else 0 |
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
154 |
|
103.1.1
by Rick Harding
Add the init flag to upgrade and tests to support it |
155 |
def upgrade(self, datastore, init): |
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
156 |
"""Run `upgrade` methods for required version files.
|
157 |
||
158 |
:param datastore: An instance of DataStore
|
|
159 |
||
160 |
"""
|
|
161 |
current_version = datastore.current_version |
|
162 |
||
163 |
if current_version is None: |
|
103.1.1
by Rick Harding
Add the init flag to upgrade and tests to support it |
164 |
if init: |
165 |
# We want to auto init the database anyway.
|
|
166 |
datastore.version_datastore() |
|
167 |
current_version = 0 |
|
168 |
else: |
|
169 |
raise Exception('Data store is not versioned') |
|
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
170 |
|
103.1.1
by Rick Harding
Add the init flag to upgrade and tests to support it |
171 |
if current_version >= self.latest: |
100.1.16
by Rick Harding
Update per code review |
172 |
# Nothing to do here. All migrations processed already.
|
100.1.2
by Rick Harding
Add the upgade command, the ability to check versions, etc. |
173 |
return None |
100.1.6
by Rick Harding
working on cli for migrate |
174 |
|
100.1.16
by Rick Harding
Update per code review |
175 |
while current_version < self.latest: |
176 |
# Let's get processing.
|
|
177 |
next_version = current_version + 1 |
|
178 |
module_name = self.versions[next_version] |
|
179 |
module = imp.load_source( |
|
180 |
module_name.strip('.py'), |
|
181 |
join(self.versions_dir, module_name)) |
|
182 |
getattr(module, 'upgrade')(datastore.db) |
|
183 |
current_version = next_version |
|
184 |
datastore.update_version(current_version) |
|
185 |
return current_version |
|
186 |
||
100.1.6
by Rick Harding
working on cli for migrate |
187 |
|
188 |
def parse_args(): |
|
189 |
desc = "Mongo migration tool." |
|
190 |
parser = argparse.ArgumentParser(description=desc) |
|
191 |
subparsers = parser.add_subparsers(help='sub-command help') |
|
192 |
||
193 |
parser_current = subparsers.add_parser('current') |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
194 |
parser_current.set_defaults(func=Commands.current) |
195 |
||
100.1.6
by Rick Harding
working on cli for migrate |
196 |
parser_latest = subparsers.add_parser('latest') |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
197 |
parser_latest.set_defaults(func=Commands.latest) |
100.1.6
by Rick Harding
working on cli for migrate |
198 |
|
199 |
parser_new = subparsers.add_parser('new') |
|
200 |
parser_new.add_argument( |
|
201 |
'-d', '--description', action='store', |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
202 |
required=True, |
100.1.6
by Rick Harding
working on cli for migrate |
203 |
help='The description of the new migration.') |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
204 |
parser_new.set_defaults(func=Commands.new) |
100.1.6
by Rick Harding
working on cli for migrate |
205 |
|
206 |
parser_upgrade = subparsers.add_parser('upgrade') |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
207 |
parser_upgrade.set_defaults(func=Commands.upgrade) |
103.1.1
by Rick Harding
Add the init flag to upgrade and tests to support it |
208 |
parser_upgrade.add_argument( |
209 |
'-i', '--init', action='store_true', |
|
210 |
default=False, |
|
211 |
help='Auto init the database if not already init.') |
|
100.1.6
by Rick Harding
working on cli for migrate |
212 |
|
213 |
args = parser.parse_args() |
|
214 |
return args |
|
215 |
||
216 |
||
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
217 |
class Commands(object): |
218 |
"""Container for the various commands the cli can execute"""
|
|
219 |
||
220 |
@staticmethod
|
|
100.1.8
by Rick Harding
Garden |
221 |
def get_datastore(ini): |
128.1.1
by Rick Harding
Big chunk of reworking db |
222 |
connection = getconnection(ini) |
223 |
db = getdb(connection, ini.get('mongo.database')) |
|
100.1.8
by Rick Harding
Garden |
224 |
ds = DataStore(db) |
225 |
return ds |
|
226 |
||
227 |
@classmethod
|
|
228 |
def current(cls, ini, args): |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
229 |
"""Fetch the current migration version of the data store."""
|
100.1.8
by Rick Harding
Garden |
230 |
ds = cls.get_datastore(ini) |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
231 |
version = ds.current_version |
232 |
||
233 |
if version is None: |
|
234 |
print 'Data store is not currently versioned.' |
|
235 |
else: |
|
236 |
print version |
|
237 |
||
238 |
@staticmethod
|
|
239 |
def latest(ini, args): |
|
240 |
"""Determine the latest migration version available."""
|
|
241 |
migrations = Versions(ini['migrations']) |
|
242 |
print migrations.latest |
|
243 |
||
244 |
@staticmethod
|
|
245 |
def new(ini, args): |
|
246 |
"""Generate a new migration file to be completed."""
|
|
247 |
migrations = Versions(ini['migrations']) |
|
248 |
filename = migrations.create_new_version_file(args.description) |
|
249 |
print "Created new migration: {0}".format(filename) |
|
250 |
||
100.1.8
by Rick Harding
Garden |
251 |
@classmethod
|
252 |
def upgrade(cls, ini, args): |
|
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
253 |
"""Upgrade the data store to the latest available migration."""
|
100.1.8
by Rick Harding
Garden |
254 |
ds = cls.get_datastore(ini) |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
255 |
migrations = Versions(ini['migrations']) |
103.1.1
by Rick Harding
Add the init flag to upgrade and tests to support it |
256 |
new_version = migrations.upgrade(ds, args.init) |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
257 |
|
258 |
if new_version is None: |
|
259 |
print 'There are no new migrations to apply' |
|
260 |
else: |
|
261 |
print "Updated the datastore to version: {0}".format(new_version) |
|
262 |
||
263 |
||
100.1.17
by Rick Harding
Add migrations cli entry point, update docs |
264 |
def main(): |
265 |
"""Target for the console entry point."""
|
|
100.1.6
by Rick Harding
working on cli for migrate |
266 |
args = parse_args() |
100.1.7
by Rick Harding
Update the migrate script with a cli that works |
267 |
ini = get_ini() |
268 |
||
269 |
# Add the migration path to the ini.
|
|
270 |
migration_path = join(abspath(dirname(__file__)), 'versions') |
|
271 |
ini['migrations'] = migration_path |
|
272 |
||
273 |
args.func(ini, args) |
|
100.1.17
by Rick Harding
Add migrations cli entry point, update docs |
274 |
|
275 |
||
276 |
if __name__ == '__main__': |
|
277 |
main() |