Merge lp:~gnuoy/charm-helpers/add-vcsdirs-module into lp:charm-helpers
- add-vcsdirs-module
- Merge into devel
Status: | Needs review |
---|---|
Proposed branch: | lp:~gnuoy/charm-helpers/add-vcsdirs-module |
Merge into: | lp:charm-helpers |
Diff against target: |
510 lines (+498/-0) 2 files modified
charmhelpers/contrib/vcsdir/__init__.py (+208/-0) tests/contrib/vcsdir/test_vcsdir.py (+290/-0) |
To merge this branch: | bzr merge lp:~gnuoy/charm-helpers/add-vcsdirs-module |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Liam Young (community) | Needs Resubmitting | ||
Matthew Wedgwood (community) | Needs Information | ||
Review via email: mp+177894@code.launchpad.net |
Commit message
Description of the change
Code to manage a payload from a version control system by creating
a directory for each revision and using a symlink to point at the
desired directory which an application server can then reference
e.g.
Point apache/gunicorn etc at /srv/code/
payload from charm:
deploy_base = VcsDirs(
deploy_
If r8 was in /var/lib/
$ ls -l /srv/code
total 4
lrwxrwxrwx 1 liam liam 23 Jul 31 16:51 my-project -> /srv/code/
drwxrwxr-x 3 liam liam 4096 Jul 31 16:41 my-project-8
Old directories can be removed with prune_dirs e.g.
deploy_
Liam Young (gnuoy) wrote : | # |
> This looks awesome, Liam. Thanks! As this is a contrib feature, I'm game for
> merging it as-is, but I see a few things that are worth discussing first.
>
> In _get_vcs_
> more specifically. If the user doesn't have the git module, for example, they
> might get back a generic "Exception" with no description of what went wrong.
> It would be helpful to provide a description when you throw the exception
> (e.g. Exception(
> git nor bzr repo, the function simply returns None. That looks problematic in
> the places it's used.
>
This was intentional as _get_vcs_identifier tries to autodetect which vcs system is in use. If it gets an exception because a particular vcs module isn't available or because the directory is not under the control of that vcs system then it moves on and tries the next one, hence not raising an exception back to the user. Do you think this approach is flawed ? or would it be ok to just make sure that if no vcs system is detected an exception, with useful message, is raised ?
> In VcsDirs.
>
> if check_newer and current_branchid >= int(candidate_
>
> I know git isn't your focus, but I'm pretty sure that the branchids won't be
> comparable as ints. If fixing that isn't trivial, I certainly wouldn't
> begrudge you to remove git support for this merge. Also (trivial), I believe
> the "check_newer" boolean in this line is redundant.
I left it up to the user to decide whether to compare the branchids based on the vcs system they're using. If the caller sets check_newer to be true when using git it will fail ( and I agree current_branchid >= int(candidate_
>
> As for the body of this conditional, did you intend to do anything besides
> report? I would have expected it to do the deploy only if the new code is
> newer.
If using bzr I'd expect check_newer to be set to true and so only a newer revno of the code is deployed. As for git, I decided not to worry and just deploy what is given
>
> Finally, I see that you move the code from its original place rather than
> copying (or preferably rsync'ing) it. I wonder if that might be somewhat
> surprising to users. I think it bears mentioning in the docs at least.
That makes sense, I'll take a look at rsync'ing it.
Matthew Wedgwood (mew) wrote : | # |
> > In _get_vcs_
> > more specifically. If the user doesn't have the git module, for example, they
> > might get back a generic "Exception" with no description of what went wrong.
> > It would be helpful to provide a description when you throw the exception
> > (e.g. Exception(
> > git nor bzr repo, the function simply returns None. That looks problematic in
> > the places it's used.
>
> This was intentional as _get_vcs_identifier tries to autodetect which vcs
> system is in use. If it gets an exception because a particular vcs module
> isn't available or because the directory is not under the control of that vcs
> system then it moves on and tries the next one, hence not raising an exception
> back to the user. Do you think this approach is flawed ? or would it be ok to
> just make sure that if no vcs system is detected an exception, with useful
> message, is raised ?
I guess my main concern is that there will be no way to distinguish between those errors. They'll simply come out as a traceback ending in "raise Exception" necessitating a look at the code to figure out what went wrong. What I would expect is a message along the lines of "<dir> is not a VCS repository. Tried <repo types>."
> > In VcsDirs.
> >
> > if check_newer and current_branchid >= int(candidate_
> >
> > I know git isn't your focus, but I'm pretty sure that the branchids won't be
> > comparable as ints. If fixing that isn't trivial, I certainly wouldn't
> > begrudge you to remove git support for this merge. Also (trivial), I believe
> > the "check_newer" boolean in this line is redundant.
>
> I left it up to the user to decide whether to compare the branchids based on
> the vcs system they're using. If the caller sets check_newer to be true when
> using git it will fail ( and I agree current_branchid >=
> int(candidate_
> take the decision about whether to compare ids away from the user and have it
> be dictated by the vcs system (bzr => yes, git => no).
I think it's useful to provide the option, as it seems quite helpful to have the library refuse to activate an older version unless it's explicitly asked to. It's still possible to compare branch IDs in git, but you have to use 'git rev-list' and work it out.
- 66. By Liam Young
-
Added support for comparing Git directories by the committed date of the head commit
- 67. By Liam Young
-
Switched to using rsync rather than move for copying src directories
Liam Young (gnuoy) wrote : | # |
I've added the following:
1) A message with the exception if _get_vcs_identifier fails to find the vcs system for the directory
2) Added support for comparing Git directories by the committed date of the head commit
3) Switched to using rsync for populating directories rather than move.
Unmerged revisions
- 67. By Liam Young
-
Switched to using rsync rather than move for copying src directories
- 66. By Liam Young
-
Added support for comparing Git directories by the committed date of the head commit
- 65. By Liam Young
-
Add VcsDirs module for managing a payload from a version control system by creating a directory for each revision
Preview Diff
1 | === added directory 'charmhelpers/contrib/vcsdir' |
2 | === added file 'charmhelpers/contrib/vcsdir/__init__.py' |
3 | --- charmhelpers/contrib/vcsdir/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ charmhelpers/contrib/vcsdir/__init__.py 2013-08-28 15:57:15 +0000 |
5 | @@ -0,0 +1,208 @@ |
6 | +# |
7 | +# Code to manage a payload from a version control system by creating |
8 | +# a directory for each revision and using a symlink to point at the |
9 | +# desired directory which an application server can then reference |
10 | +# e.g. |
11 | +# Point apache/gunicorn etc at /srv/code/my-project and deploy |
12 | +# payload from charm: |
13 | +# |
14 | +# deploy_base = VcsDirs('/srv/code', 'my-project') |
15 | +# deploy_base.deploy_dir('/var/lib/juju/charm/new-code') |
16 | +# |
17 | +# If r8 was in /var/lib/juju/charm/new-code then this would result in: |
18 | +# $ ls -l /srv/code |
19 | +# total 4 |
20 | +# lrwxrwxrwx 1 liam liam 23 Jul 31 16:51 my-project -> /srv/code/my-project-8 |
21 | +# drwxrwxr-x 3 liam liam 4096 Jul 31 16:41 my-project-8 |
22 | +# |
23 | +# Old directories can be removed with prune_dirs e.g. |
24 | +# deploy_base.prune_dirs(keep_dirs=2, order_by_revno=True) |
25 | + |
26 | + |
27 | +import glob |
28 | +import os |
29 | +import shutil |
30 | +from charmhelpers.core import host |
31 | + |
32 | + |
33 | +def _get_vcs_identifier(source): |
34 | + """Return identifier for branch and a value which can be |
35 | + used to inspect chronological order of branches""" |
36 | + try: |
37 | + from git import Repo |
38 | + repo = Repo(source) |
39 | + # Return sha for the branch id and use commited date for ordering with |
40 | + # other branches |
41 | + return (repo.head.commit.hexsha, repo.head.commit.committed_date) |
42 | + except: |
43 | + pass |
44 | + try: |
45 | + from bzrlib.branch import Branch |
46 | + current_branch = Branch.open(source) |
47 | + # Return branch revno for both the branch id and also use it for |
48 | + # ordering with other branches |
49 | + return (current_branch.revno(), current_branch.revno()) |
50 | + except: |
51 | + raise Exception(source + " is not a VCS controlled repository. Tried Git and Bzr") |
52 | + |
53 | + |
54 | +class VcsDir(): |
55 | + """A VcsDir is a directory which is managed by a vcs system and has a name |
56 | + in format codename-branchid. Where branchid is the id for the |
57 | + branch e.g. if using bzr it is the bzr revno.""" |
58 | + def __init__(self, dir): |
59 | + self.fqdir = dir |
60 | + self.basename = os.path.basename(self.fqdir) |
61 | + self.dirname = os.path.dirname(self.fqdir) |
62 | + if os.path.exists(self.fqdir) and not os.path.isdir(self.fqdir): |
63 | + raise Exception |
64 | + |
65 | + def get_fqdir(self): |
66 | + """Get the full path to the directory""" |
67 | + return self.fqdir |
68 | + |
69 | + def get_basename(self): |
70 | + """Return directory without path""" |
71 | + return self.basename |
72 | + |
73 | + def get_codename(self): |
74 | + """Return the project codename""" |
75 | + return self.basename.rpartition('-')[0] |
76 | + |
77 | + def get_branchid(self): |
78 | + """Return the vcs identifier for the branch""" |
79 | + return self.basename.rsplit('-')[-1] |
80 | + |
81 | + def get_comparator(self): |
82 | + """Return the vcs comparator for the branch""" |
83 | + (_, vcs_comparator) = _get_vcs_identifier(self.fqdir) |
84 | + return vcs_comparator |
85 | + |
86 | + def validate_name(self): |
87 | + """Check the branchid in the dir name matches the code branchid""" |
88 | + (vcs_identifier, _) = _get_vcs_identifier(self.fqdir) |
89 | + if str(self.get_branchid()) == str(vcs_identifier): |
90 | + return True |
91 | + else: |
92 | + return False |
93 | + |
94 | + |
95 | +class VcsDirs(): |
96 | + """A collection of VcsDir directories with a symlink pointing at the |
97 | + desired directory which an application (apache, gunicorn etc) can |
98 | + use""" |
99 | + def __init__(self, dir, code_name): |
100 | + self.fqdir = dir |
101 | + self.code_name = code_name |
102 | + self.dir_glob = os.path.join(self.fqdir, code_name + '-*') |
103 | + |
104 | + def add_code_symlink(self, branchid): |
105 | + """Create a symlink pointing at the requestred vcsdir""" |
106 | + symlink = self.get_currentcode_symlink() |
107 | + source_dir = self.get_directory_by_branchid(branchid) |
108 | + if os.path.islink(symlink): |
109 | + os.remove(symlink) |
110 | + os.symlink(source_dir, symlink) |
111 | + |
112 | + def get_currentcode_symlink(self): |
113 | + """Return full path to currentcode symlink""" |
114 | + return os.path.join(self.fqdir, self.code_name) |
115 | + |
116 | + def get_currentcode_symlink_target(self): |
117 | + """Return the target of the currentcode symlink""" |
118 | + return os.path.realpath(self.get_currentcode_symlink()) |
119 | + |
120 | + def get_currentcode_branchid(self): |
121 | + """Return the branchid of the vcsdir that the currentcode symlinki is |
122 | + currently pointing at""" |
123 | + new_vcsdir = VcsDir(self.get_currentcode_symlink_target()) |
124 | + branchid = new_vcsdir.get_branchid() |
125 | + return branchid |
126 | + |
127 | + def get_branchids(self): |
128 | + """Get a list of all the branchids available""" |
129 | + branchids = set() |
130 | + for direc in glob.glob(self.dir_glob): |
131 | + if os.path.isdir(direc): |
132 | + branchids.add(VcsDir(direc).get_branchid()) |
133 | + return branchids |
134 | + |
135 | + def get_directories_key_branchid(self): |
136 | + """Get a hash of all the vcs directories with branchid as the key""" |
137 | + branch_ids = {} |
138 | + for direc in glob.glob(self.dir_glob): |
139 | + if os.path.isdir(direc): |
140 | + new_vcsdir = VcsDir(direc) |
141 | + new_vcsdir.validate_name() |
142 | + branchid = new_vcsdir.get_branchid() |
143 | + if branchid in branch_ids: |
144 | + continue |
145 | + else: |
146 | + branch_ids[branchid] = direc |
147 | + return branch_ids |
148 | + |
149 | + def get_directories_key_comparator(self): |
150 | + """Get the comparator of all the vcs directories with branchid as |
151 | + the key""" |
152 | + branch_comps = {} |
153 | + for direc in glob.glob(self.dir_glob): |
154 | + if os.path.isdir(direc): |
155 | + new_vcsdir = VcsDir(direc) |
156 | + branch_comp = new_vcsdir.get_comparator() |
157 | + if branch_comp in branch_comps: |
158 | + continue |
159 | + else: |
160 | + branch_comps[branch_comp] = direc |
161 | + return branch_comps |
162 | + |
163 | + def get_directory_by_branchid(self, branchid): |
164 | + """Return the directory for a given branchid""" |
165 | + branch_ids = self.get_directories_key_branchid() |
166 | + if branchid in branch_ids: |
167 | + return branch_ids[branchid] |
168 | + return None |
169 | + |
170 | + def is_branchid_present(self, branchid): |
171 | + """Is a directory containing a particular branchid present """ |
172 | + if branchid in self.get_branchids(): |
173 | + return True |
174 | + return False |
175 | + |
176 | + def prune_dirs(self, keep_dirs=3, order_by_revno=False): |
177 | + """Delete the oldest 'keep_dirs' number of directories. Delete the |
178 | + oldest by directory mtime unless order_by_revno is True""" |
179 | + delete_count = 0 |
180 | + direc = self.get_directories_key_comparator() |
181 | + number_to_delete = len(direc) - keep_dirs |
182 | + if order_by_revno: |
183 | + sorted_ids = sorted(direc.iterkeys()) |
184 | + else: |
185 | + branchid_dates = [(branchid, os.stat(direc[branchid]).st_mtime) |
186 | + for branchid in direc.keys()] |
187 | + sorted_ids = [r[0] |
188 | + for r in sorted(branchid_dates, key=lambda r: r[1])] |
189 | + for branchid in sorted_ids: |
190 | + if delete_count < number_to_delete: |
191 | + if direc[branchid] != self.get_currentcode_symlink_target(): |
192 | + shutil.rmtree(direc[branchid]) |
193 | + delete_count += 1 |
194 | + |
195 | + def deploy_dir(self, srcdir, check_newer=False): |
196 | + """Move a new directory in and update the currentcode symlink to point |
197 | + at it. check_newer assumes vcs type supports rA > rb""" |
198 | + (candidate_branchid, candidate_comparator) = _get_vcs_identifier(srcdir) |
199 | + branchids = self.get_branchids() |
200 | + if candidate_branchid in branchids: |
201 | + print "Directory containing branchid already exists" |
202 | + return |
203 | + if check_newer and os.path.exists(self.get_currentcode_symlink()): |
204 | + (_, current_comparator) = _get_vcs_identifier(self.get_currentcode_symlink_target()) |
205 | + if check_newer and current_comparator >= int(candidate_comparator): |
206 | + print "ERROR. Not deploying branch. Live branch (%s) >= candidate branch(%s)" % \ |
207 | + (current_comparator, candidate_comparator) |
208 | + return |
209 | + # Move new directiry in and point currenctcode symlink at it |
210 | + host.rsync(srcdir + os.sep, os.path.join(self.fqdir, |
211 | + self.code_name + |
212 | + "-" + str(candidate_branchid))) |
213 | + self.add_code_symlink(branchid=str(candidate_branchid)) |
214 | |
215 | === added directory 'tests/contrib/vcsdir' |
216 | === added file 'tests/contrib/vcsdir/__init__.py' |
217 | === added file 'tests/contrib/vcsdir/test_vcsdir.py' |
218 | --- tests/contrib/vcsdir/test_vcsdir.py 1970-01-01 00:00:00 +0000 |
219 | +++ tests/contrib/vcsdir/test_vcsdir.py 2013-08-28 15:57:15 +0000 |
220 | @@ -0,0 +1,290 @@ |
221 | +from testtools import TestCase |
222 | +import glob |
223 | +from mock import patch, call, Mock |
224 | +import charmhelpers.contrib.vcsdir as vcsdir |
225 | +from charmhelpers.core import host |
226 | +import os |
227 | +import shutil |
228 | + |
229 | +DISTINCT_BRANCHIDS = ['aa', 'bb', 'cc'] |
230 | +PROJECT_NAME = 'my-super-whizzy-project' |
231 | +BASE_DIR = '/srv' |
232 | + |
233 | +tmpbranchids = DISTINCT_BRANCHIDS * 2 |
234 | +FAKE_VCSDIRS = [] |
235 | + |
236 | +for branchid in tmpbranchids: |
237 | + directory = os.path.join(BASE_DIR, |
238 | + PROJECT_NAME + '-' + branchid) |
239 | + FAKE_VCSDIRS.append([directory, branchid]) |
240 | +TESTDIR = {} |
241 | +TESTDIR['direc'] = FAKE_VCSDIRS[0][0] |
242 | +TESTDIR['branchid'] = FAKE_VCSDIRS[0][1] |
243 | +NEWDIR = {} |
244 | +NEWDIR['direc'] = os.path.join(BASE_DIR, 'new-dir') |
245 | +NEWDIR['branchid'] = 'zz' |
246 | + |
247 | + |
248 | +class mock_git(): |
249 | + class Repo(): |
250 | + def __init__(self, source): |
251 | + self.head.commit.hexsha = "gittest" |
252 | + self.head.commit.committed_date = 1374676968 |
253 | + |
254 | + class head(): |
255 | + class commit(): |
256 | + pass |
257 | + |
258 | + |
259 | +class BzrBranch(): |
260 | + def revno(self): |
261 | + return 10 |
262 | + |
263 | + |
264 | +class mock_bzrlib_branch(): |
265 | + class Branch(): |
266 | + @staticmethod |
267 | + def open(source): |
268 | + return BzrBranch() |
269 | + |
270 | + |
271 | +class vcsidentifierTest(TestCase): |
272 | + def test_get_vcs_identifier_git_exception(self): |
273 | + self.assertRaises(Exception, lambda: vcsdir._get_vcs_identifier('tmp')) |
274 | + |
275 | + @patch.dict('sys.modules', {'git': mock_git}) |
276 | + def test_get_vcs_identifier_git(self): |
277 | + self.assertEqual(vcsdir._get_vcs_identifier('tmp'), ("gittest", 1374676968)) |
278 | + |
279 | + @patch.dict('sys.modules', {'bzrlib.branch': mock_bzrlib_branch}) |
280 | + def test_get_vcs_identifier_bzr(self): |
281 | + self.assertEqual(vcsdir._get_vcs_identifier('tmp'), (10, 10)) |
282 | + |
283 | + |
284 | +class VcsDirTest(TestCase): |
285 | + @patch.object(os.path, 'isdir') |
286 | + @patch.object(os.path, 'exists') |
287 | + def test_fail_nodir(self, mock_pathexists, mock_pathisdir): |
288 | + mock_pathexists.return_value = True |
289 | + mock_pathisdir.return_value = False |
290 | + self.assertRaises(Exception, lambda: vcsdir.VcsDir('/tmp')) |
291 | + |
292 | + def test_get_fqdir(self): |
293 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
294 | + self.assertEqual(TESTDIR['direc'], vcstestdir.get_fqdir()) |
295 | + |
296 | + def test_get_basename(self): |
297 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
298 | + self.assertEqual(TESTDIR['direc'].replace(BASE_DIR + os.sep, ''), |
299 | + vcstestdir.get_basename()) |
300 | + |
301 | + def test_get_codename(self): |
302 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
303 | + self.assertEqual(PROJECT_NAME, vcstestdir.get_codename()) |
304 | + |
305 | + def test_get_branchid(self): |
306 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
307 | + self.assertEqual('aa', vcstestdir.get_branchid()) |
308 | + |
309 | + @patch.object(vcsdir, '_get_vcs_identifier') |
310 | + def test_get_comparator(self, mock_get_vcs_identifier): |
311 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
312 | + mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[0], DISTINCT_BRANCHIDS[0]) |
313 | + self.assertEqual('aa', vcstestdir.get_comparator()) |
314 | + |
315 | + @patch.object(vcsdir, '_get_vcs_identifier') |
316 | + def test_validate_name(self, mock_get_vcs_identifier): |
317 | + vcstestdir = vcsdir.VcsDir(TESTDIR['direc']) |
318 | + mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[0], DISTINCT_BRANCHIDS[0]) |
319 | + self.assertEqual(True, vcstestdir.validate_name()) |
320 | + mock_get_vcs_identifier.return_value = (DISTINCT_BRANCHIDS[1], DISTINCT_BRANCHIDS[1]) |
321 | + self.assertEqual(False, vcstestdir.validate_name()) |
322 | + |
323 | + |
324 | +class VcsDirsTest(TestCase): |
325 | + @patch.object(os, 'remove') |
326 | + @patch.object(os.path, 'islink') |
327 | + @patch.object(os, 'symlink') |
328 | + @patch.object(vcsdir.VcsDirs, 'get_directory_by_branchid') |
329 | + def test_add_code_symlink(self, mock_get_directory_by_branchid, |
330 | + mock_symlink, mock_pathislink, mock_remove): |
331 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
332 | + code_symlink = os.path.join(BASE_DIR, PROJECT_NAME) |
333 | + mock_pathislink.return_value = True |
334 | + mock_get_directory_by_branchid.return_value = TESTDIR['direc'] |
335 | + testdir.add_code_symlink(branchid=TESTDIR['branchid']) |
336 | + mock_symlink.assert_called_with(TESTDIR['direc'], code_symlink) |
337 | + |
338 | + @patch.object(vcsdir.VcsDir, 'get_branchid') |
339 | + @patch.object(vcsdir.VcsDirs, 'get_currentcode_symlink') |
340 | + def test_get_currentcode_branchid(self, mock_get_currentcode_symlink, |
341 | + mock_get_branchid): |
342 | + mock_get_currentcode_symlink.return_value = os.path.join(BASE_DIR, |
343 | + PROJECT_NAME) |
344 | + mock_get_branchid.return_value = TESTDIR['branchid'] |
345 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
346 | + self.assertEqual(testdir.get_currentcode_branchid(), |
347 | + TESTDIR['branchid']) |
348 | + |
349 | + @patch.object(os.path, 'isdir') |
350 | + @patch.object(vcsdir.VcsDir, 'get_branchid') |
351 | + @patch.object(glob, 'glob') |
352 | + def test_get_branchids(self, mock_glob, mock_get_branchid, |
353 | + mock_ospathisdir): |
354 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
355 | + directories = [x[0] for x in FAKE_VCSDIRS] |
356 | + |
357 | + def branchid_side_effect(): |
358 | + return branchids.pop() |
359 | + mock_get_branchid.side_effect = branchid_side_effect |
360 | + mock_glob.return_value = directories |
361 | + mock_ospathisdir.return_value = True |
362 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
363 | + branchids = testdir.get_branchids() |
364 | + # Check we have all the distinct branchids |
365 | + self.assertEqual(branchids, set([x[1] for x in FAKE_VCSDIRS])) |
366 | + |
367 | + @patch.object(os.path, 'isdir') |
368 | + @patch.object(vcsdir.VcsDir, 'validate_name') |
369 | + @patch.object(vcsdir.VcsDir, 'get_branchid') |
370 | + @patch.object(glob, 'glob') |
371 | + def test_get_directories_key_branchid(self, mock_glob, |
372 | + mock_get_branchid, |
373 | + mock_validate_name, |
374 | + mock_ospathisdir): |
375 | + vcsdirs = [x[0] for x in FAKE_VCSDIRS] |
376 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
377 | + mock_glob.return_value = vcsdirs |
378 | + mock_validate_name.return_value = True |
379 | + mock_ospathisdir.return_value = True |
380 | + |
381 | + def branchid_side_effect(): |
382 | + return branchids.pop(0) |
383 | + mock_get_branchid.side_effect = branchid_side_effect |
384 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
385 | + branchids = testdir.get_directories_key_branchid() |
386 | + self.assertEqual(branchids[TESTDIR['branchid']], TESTDIR['direc']) |
387 | + |
388 | + @patch.object(os.path, 'isdir') |
389 | + @patch.object(vcsdir.VcsDir, 'get_comparator') |
390 | + @patch.object(glob, 'glob') |
391 | + def test_get_directories_key_comparator(self, mock_glob, |
392 | + mock_get_comparator, |
393 | + mock_ospathisdir): |
394 | + vcsdirs = [x[0] for x in FAKE_VCSDIRS] |
395 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
396 | + mock_glob.return_value = vcsdirs |
397 | + mock_ospathisdir.return_value = True |
398 | + |
399 | + def branchid_side_effect(): |
400 | + return branchids.pop(0) |
401 | + mock_get_comparator.side_effect = branchid_side_effect |
402 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
403 | + branchids = testdir.get_directories_key_comparator() |
404 | + self.assertEqual(branchids[TESTDIR['branchid']], TESTDIR['direc']) |
405 | + |
406 | + @patch.object(vcsdir.VcsDirs, 'get_branchids') |
407 | + def test_is_branchid_present(self, mock_get_branchids): |
408 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
409 | + mock_get_branchids.return_value = branchids |
410 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
411 | + self.assertEqual(True, testdir.is_branchid_present(branchids[0])) |
412 | + self.assertEqual(False, |
413 | + testdir.is_branchid_present('nonexistantbranchid')) |
414 | + |
415 | + @patch.object(vcsdir.VcsDirs, 'get_directories_key_branchid') |
416 | + def test_get_directory_by_branchid(self, |
417 | + mock_get_directories_key_branchid): |
418 | + branchids = {} |
419 | + for branch in FAKE_VCSDIRS: |
420 | + branchids[branch[1]] = branch[0] |
421 | + mock_get_directories_key_branchid.return_value = branchids |
422 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
423 | + self.assertEqual(FAKE_VCSDIRS[0][0], |
424 | + testdir.get_directory_by_branchid( |
425 | + FAKE_VCSDIRS[0][1])) |
426 | + self.assertEqual(None, |
427 | + testdir.get_directory_by_branchid('nonexistantid')) |
428 | + |
429 | + @patch.object(vcsdir.VcsDirs, 'get_directories_key_comparator') |
430 | + @patch.object(shutil, 'rmtree') |
431 | + @patch.object(os, 'stat') |
432 | + def test_prune_dirs_by_ctime(self, mock_osstat, mock_rmtree, |
433 | + mock_get_directories_key_comparator): |
434 | + branchids = {} |
435 | + dirctimes = {} |
436 | + base_time = 1200000000 |
437 | + for branch in FAKE_VCSDIRS: |
438 | + branchids[branch[1]] = branch[0] |
439 | + base_time = base_time + 1 |
440 | + dirctimes[branch[0]] = str(base_time) + ".00000" |
441 | + mock_get_directories_key_comparator.return_value = branchids |
442 | + |
443 | + def osstat_side_effect(bob): |
444 | + mtime = dirctimes[bob] |
445 | + return Mock(st_mtime=mtime) |
446 | + mock_osstat.side_effect = osstat_side_effect |
447 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
448 | + testdir.prune_dirs(keep_dirs=1) |
449 | + calls = [call(FAKE_VCSDIRS[0][0]), call(FAKE_VCSDIRS[1][0])] |
450 | + mock_rmtree.assert_has_calls(calls) |
451 | + |
452 | + @patch.object(vcsdir.VcsDirs, 'get_directories_key_comparator') |
453 | + @patch.object(shutil, 'rmtree') |
454 | + def test_prune_dirs_by_revno(self, mock_rmtree, |
455 | + mock_get_directories_key_comparator): |
456 | + branch_comps = {} |
457 | + for branch in FAKE_VCSDIRS: |
458 | + branch_comps[branch[1]] = branch[0] |
459 | + |
460 | + mock_get_directories_key_comparator.return_value = branch_comps |
461 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
462 | + testdir.prune_dirs(keep_dirs=1, order_by_revno=True) |
463 | + calls = [call(FAKE_VCSDIRS[0][0]), call(FAKE_VCSDIRS[1][0])] |
464 | + mock_rmtree.assert_has_calls(calls) |
465 | + |
466 | + @patch.object(vcsdir.VcsDirs, 'add_code_symlink') |
467 | + @patch.object(vcsdir.VcsDirs, 'get_branchids') |
468 | + @patch.object(vcsdir, '_get_vcs_identifier') |
469 | + @patch.object(host, 'rsync') |
470 | + def test_deploy_dir_newdir(self, mock_rsync, mock_get_vcs_identifier, |
471 | + mock_get_branchids, mock_add_code_symlink): |
472 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
473 | + mock_get_branchids.return_value = branchids |
474 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
475 | + mock_get_vcs_identifier.return_value = (NEWDIR['branchid'], NEWDIR['branchid']) |
476 | + testdir.deploy_dir(NEWDIR['direc']) |
477 | + new_vcsdir = os.path.join(BASE_DIR, |
478 | + PROJECT_NAME + '-' + NEWDIR['branchid']) |
479 | + mock_rsync.assert_called_with(NEWDIR['direc'] + os.sep, new_vcsdir) |
480 | + |
481 | + @patch.object(vcsdir.VcsDirs, 'add_code_symlink') |
482 | + @patch.object(vcsdir.VcsDirs, 'get_branchids') |
483 | + @patch.object(vcsdir, '_get_vcs_identifier') |
484 | + @patch.object(host, 'rsync') |
485 | + def test_deploy_dir_same_branchid(self, mock_rsync, |
486 | + mock_get_vcs_identifier, |
487 | + mock_get_branchids, |
488 | + mock_add_code_symlink): |
489 | + mock_get_vcs_identifier.return_value = (FAKE_VCSDIRS[0][1], FAKE_VCSDIRS[0][1]) |
490 | + branchids = [x[1] for x in FAKE_VCSDIRS] |
491 | + mock_get_branchids.return_value = branchids |
492 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
493 | + testdir.deploy_dir(NEWDIR['direc']) |
494 | + assert not mock_rsync.called |
495 | + assert not mock_add_code_symlink.called |
496 | + |
497 | + @patch.object(vcsdir.VcsDirs, 'get_currentcode_branchid') |
498 | + @patch.object(os.path, 'exists') |
499 | + @patch.object(vcsdir, '_get_vcs_identifier') |
500 | + @patch.object(host, 'rsync') |
501 | + def test_deploy_dir_lower_branchid(self, mock_rsync, |
502 | + mock_get_vcs_identifier, |
503 | + mock_ospathexists, |
504 | + mock_get_currentcode_branchid): |
505 | + mock_ospathexists.return_value = True |
506 | + mock_get_vcs_identifier.return_value = ('1', '1') |
507 | + mock_get_currentcode_branchid.return_value = '2' |
508 | + testdir = vcsdir.VcsDirs(BASE_DIR, PROJECT_NAME) |
509 | + testdir.deploy_dir(NEWDIR['direc'], check_newer=True) |
510 | + assert not mock_rsync.called |
This looks awesome, Liam. Thanks! As this is a contrib feature, I'm game for merging it as-is, but I see a few things that are worth discussing first.
In _get_vcs_ identifier, I think it would be best to catch exceptions a bit more specifically. If the user doesn't have the git module, for example, they might get back a generic "Exception" with no description of what went wrong. It would be helpful to provide a description when you throw the exception (e.g. Exception( "Something bad happened")). Also, if the srcdir is neither a git nor bzr repo, the function simply returns None. That looks problematic in the places it's used.
In VcsDirs. deploy_ dir() (line 174), I see:
if check_newer and current_branchid >= int(candidate_ branchid) :
I know git isn't your focus, but I'm pretty sure that the branchids won't be comparable as ints. If fixing that isn't trivial, I certainly wouldn't begrudge you to remove git support for this merge. Also (trivial), I believe the "check_newer" boolean in this line is redundant.
As for the body of this conditional, did you intend to do anything besides report? I would have expected it to do the deploy only if the new code is newer.
Finally, I see that you move the code from its original place rather than copying (or preferably rsync'ing) it. I wonder if that might be somewhat surprising to users. I think it bears mentioning in the docs at least.