Merge lp:~bac/charms/oneiric/buildbot-master/bbm-pst into lp:~yellow/charms/oneiric/buildbot-master/trunk
- Oneiric (11.10)
- bbm-pst
- Merge into trunk
Proposed by
Brad Crittenden
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | 37 |
Merged at revision: | 37 |
Proposed branch: | lp:~bac/charms/oneiric/buildbot-master/bbm-pst |
Merge into: | lp:~yellow/charms/oneiric/buildbot-master/trunk |
Diff against target: |
748 lines (+107/-429) 8 files modified
hooks/buildbot-relation-changed (+1/-1) hooks/config-changed (+15/-11) hooks/helpers.py (+13/-237) hooks/install (+66/-6) hooks/local.py (+5/-3) hooks/start (+2/-1) hooks/stop (+3/-2) hooks/tests.py (+2/-168) |
To merge this branch: | bzr merge lp:~bac/charms/oneiric/buildbot-master/bbm-pst |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+95279@code.launchpad.net |
Commit message
Description of the change
Move generic, non-juju-related methods out of helpers and into a new package python-
The install hook must now add the apt repository for the PPA, install the package, and ensure local and helpers are not imported until that is done since they depend on shelltoolbox. The other hooks can just import and use shelltoolbox normally since we can assume the install hook did its job.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hooks/buildbot-relation-changed' | |||
2 | --- hooks/buildbot-relation-changed 2012-02-13 12:26:29 +0000 | |||
3 | +++ hooks/buildbot-relation-changed 2012-02-29 22:49:25 +0000 | |||
4 | @@ -4,6 +4,7 @@ | |||
5 | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). |
6 | 5 | 5 | ||
7 | 6 | import os | 6 | import os |
8 | 7 | from shelltoolbox import su | ||
9 | 7 | 8 | ||
10 | 8 | from helpers import ( | 9 | from helpers import ( |
11 | 9 | log, | 10 | log, |
12 | @@ -11,7 +12,6 @@ | |||
13 | 11 | log_exit, | 12 | log_exit, |
14 | 12 | relation_get, | 13 | relation_get, |
15 | 13 | relation_set, | 14 | relation_set, |
16 | 14 | su, | ||
17 | 15 | ) | 15 | ) |
18 | 16 | from local import ( | 16 | from local import ( |
19 | 17 | buildbot_reconfig, | 17 | buildbot_reconfig, |
20 | 18 | 18 | ||
21 | === modified file 'hooks/config-changed' | |||
22 | --- hooks/config-changed 2012-02-23 21:50:41 +0000 | |||
23 | +++ hooks/config-changed 2012-02-29 22:49:25 +0000 | |||
24 | @@ -7,23 +7,24 @@ | |||
25 | 7 | import json | 7 | import json |
26 | 8 | import os | 8 | import os |
27 | 9 | import os.path | 9 | import os.path |
28 | 10 | from shelltoolbox import ( | ||
29 | 11 | apt_get_install, | ||
30 | 12 | command, | ||
31 | 13 | DictDiffer, | ||
32 | 14 | get_user_ids, | ||
33 | 15 | install_extra_repositories, | ||
34 | 16 | run, | ||
35 | 17 | su, | ||
36 | 18 | ) | ||
37 | 10 | import shutil | 19 | import shutil |
38 | 11 | import subprocess | 20 | import subprocess |
39 | 12 | import sys | 21 | import sys |
40 | 13 | 22 | ||
41 | 14 | from helpers import ( | 23 | from helpers import ( |
42 | 15 | apt_get_install, | ||
43 | 16 | cd, | ||
44 | 17 | command, | ||
45 | 18 | DictDiffer, | ||
46 | 19 | get_config, | 24 | get_config, |
47 | 20 | get_user_ids, | ||
48 | 21 | install_extra_repository, | ||
49 | 22 | log, | 25 | log, |
50 | 23 | log_entry, | 26 | log_entry, |
51 | 24 | log_exit, | 27 | log_exit, |
52 | 25 | run, | ||
53 | 26 | su, | ||
54 | 27 | ) | 28 | ) |
55 | 28 | from local import ( | 29 | from local import ( |
56 | 29 | buildbot_create, | 30 | buildbot_create, |
57 | @@ -31,8 +32,6 @@ | |||
58 | 31 | config_json, | 32 | config_json, |
59 | 32 | fetch_history, | 33 | fetch_history, |
60 | 33 | generate_string, | 34 | generate_string, |
61 | 34 | get_bucket, | ||
62 | 35 | get_key, | ||
63 | 36 | get_wrapper_cfg_path, | 35 | get_wrapper_cfg_path, |
64 | 37 | put_history, | 36 | put_history, |
65 | 38 | slave_json, | 37 | slave_json, |
66 | @@ -75,7 +74,12 @@ | |||
67 | 75 | added_or_changed = diff.added_or_changed | 74 | added_or_changed = diff.added_or_changed |
68 | 76 | 75 | ||
69 | 77 | if extra_repo and 'extra-repository' in added_or_changed: | 76 | if extra_repo and 'extra-repository' in added_or_changed: |
71 | 78 | install_extra_repository(extra_repo) | 77 | try: |
72 | 78 | install_extra_repositories(extra_repo) | ||
73 | 79 | except subprocess.CalledProcessError as e: | ||
74 | 80 | log('Error adding repository: ' + extra_repo) | ||
75 | 81 | log(e) | ||
76 | 82 | raise | ||
77 | 79 | restart_required = True | 83 | restart_required = True |
78 | 80 | if extra_pkgs and 'extra-packages' in added_or_changed: | 84 | if extra_pkgs and 'extra-packages' in added_or_changed: |
79 | 81 | apt_get_install( | 85 | apt_get_install( |
80 | 82 | 86 | ||
81 | === modified file 'hooks/helpers.py' | |||
82 | --- hooks/helpers.py 2012-02-14 17:00:53 +0000 | |||
83 | +++ hooks/helpers.py 2012-02-29 22:49:25 +0000 | |||
84 | @@ -5,37 +5,24 @@ | |||
85 | 5 | 5 | ||
86 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
87 | 7 | __all__ = [ | 7 | __all__ = [ |
88 | 8 | 'apt_get_install', | ||
89 | 9 | 'cd', | ||
90 | 10 | 'command', | ||
91 | 11 | 'DictDiffer', | ||
92 | 12 | 'get_config', | 8 | 'get_config', |
93 | 13 | 'get_user_ids', | ||
94 | 14 | 'get_user_home', | ||
95 | 15 | 'get_value_from_line', | ||
96 | 16 | 'grep', | ||
97 | 17 | 'install_extra_repository', | ||
98 | 18 | 'log', | 9 | 'log', |
99 | 19 | 'log_entry', | 10 | 'log_entry', |
100 | 20 | 'log_exit', | 11 | 'log_exit', |
101 | 21 | 'run', | ||
102 | 22 | 'relation_get', | 12 | 'relation_get', |
103 | 23 | 'relation_set', | 13 | 'relation_set', |
104 | 24 | 'su', | ||
105 | 25 | 'unit_info', | 14 | 'unit_info', |
106 | 26 | ] | 15 | ] |
107 | 27 | 16 | ||
108 | 28 | from collections import namedtuple | 17 | from collections import namedtuple |
109 | 29 | from contextlib import contextmanager | ||
110 | 30 | import json | 18 | import json |
111 | 31 | import operator | 19 | import operator |
117 | 32 | import os | 20 | from shelltoolbox import ( |
118 | 33 | import pwd | 21 | command, |
119 | 34 | import re | 22 | run, |
120 | 35 | import subprocess | 23 | script_name, |
121 | 36 | import sys | 24 | ) |
122 | 37 | import tempfile | 25 | import tempfile |
123 | 38 | from textwrap import dedent | ||
124 | 39 | import time | 26 | import time |
125 | 40 | import urllib2 | 27 | import urllib2 |
126 | 41 | import yaml | 28 | import yaml |
127 | @@ -43,49 +30,15 @@ | |||
128 | 43 | 30 | ||
129 | 44 | Env = namedtuple('Env', 'uid gid home') | 31 | Env = namedtuple('Env', 'uid gid home') |
130 | 45 | 32 | ||
131 | 46 | |||
132 | 47 | def run(*args): | ||
133 | 48 | """Run the command with the given arguments. | ||
134 | 49 | |||
135 | 50 | The first argument is the path to the command to run, subsequent arguments | ||
136 | 51 | are command-line arguments to be passed. | ||
137 | 52 | """ | ||
138 | 53 | process = subprocess.Popen(args, stdout=subprocess.PIPE, | ||
139 | 54 | stderr=subprocess.PIPE, close_fds=True) | ||
140 | 55 | stdout, stderr = process.communicate() | ||
141 | 56 | if process.returncode: | ||
142 | 57 | raise subprocess.CalledProcessError( | ||
143 | 58 | process.returncode, repr(args), output=stdout+stderr) | ||
144 | 59 | return stdout | ||
145 | 60 | |||
146 | 61 | |||
147 | 62 | def command(*base_args): | ||
148 | 63 | """Return a callable that will run the given command with any arguments. | ||
149 | 64 | |||
150 | 65 | The first argument is the path to the command to run, subsequent arguments | ||
151 | 66 | are command-line arguments to "bake into" the returned callable. | ||
152 | 67 | |||
153 | 68 | The callable runs the given executable and also takes arguments that will | ||
154 | 69 | be appeneded to the "baked in" arguments. | ||
155 | 70 | |||
156 | 71 | For example, this code will list a file named "foo" (if it exists): | ||
157 | 72 | |||
158 | 73 | ls_foo = command('/bin/ls', 'foo') | ||
159 | 74 | ls_foo() | ||
160 | 75 | |||
161 | 76 | While this invocation will list "foo" and "bar" (assuming they exist): | ||
162 | 77 | |||
163 | 78 | ls_foo('bar') | ||
164 | 79 | """ | ||
165 | 80 | def callable_command(*args): | ||
166 | 81 | all_args = base_args + args | ||
167 | 82 | return run(*all_args) | ||
168 | 83 | |||
169 | 84 | return callable_command | ||
170 | 85 | |||
171 | 86 | |||
172 | 87 | log = command('juju-log') | 33 | log = command('juju-log') |
174 | 88 | apt_get_install = command('apt-get', 'install', '-y', '--force-yes') | 34 | |
175 | 35 | |||
176 | 36 | def log_entry(): | ||
177 | 37 | log("--> Entering {}".format(script_name())) | ||
178 | 38 | |||
179 | 39 | |||
180 | 40 | def log_exit(): | ||
181 | 41 | log("<-- Exiting {}".format(script_name())) | ||
182 | 89 | 42 | ||
183 | 90 | 43 | ||
184 | 91 | def get_config(): | 44 | def get_config(): |
185 | @@ -93,16 +46,6 @@ | |||
186 | 93 | return json.loads(config_get()) | 46 | return json.loads(config_get()) |
187 | 94 | 47 | ||
188 | 95 | 48 | ||
189 | 96 | def install_extra_repository(extra_repository): | ||
190 | 97 | try: | ||
191 | 98 | run('apt-add-repository', extra_repository) | ||
192 | 99 | run('apt-get', 'update') | ||
193 | 100 | except subprocess.CalledProcessError as e: | ||
194 | 101 | log('Error adding repository: ' + extra_repository) | ||
195 | 102 | log(e) | ||
196 | 103 | raise | ||
197 | 104 | |||
198 | 105 | |||
199 | 106 | def relation_get(*args): | 49 | def relation_get(*args): |
200 | 107 | cmd = command('relation-get') | 50 | cmd = command('relation-get') |
201 | 108 | return cmd(*args).strip() | 51 | return cmd(*args).strip() |
202 | @@ -114,96 +57,6 @@ | |||
203 | 114 | return cmd(*args) | 57 | return cmd(*args) |
204 | 115 | 58 | ||
205 | 116 | 59 | ||
206 | 117 | def grep(content, filename): | ||
207 | 118 | with open(filename) as f: | ||
208 | 119 | for line in f: | ||
209 | 120 | if re.match(content, line): | ||
210 | 121 | return line.strip() | ||
211 | 122 | |||
212 | 123 | |||
213 | 124 | def get_value_from_line(line): | ||
214 | 125 | return line.split('=')[1].strip('"\' ') | ||
215 | 126 | |||
216 | 127 | |||
217 | 128 | def script_name(): | ||
218 | 129 | return os.path.basename(sys.argv[0]) | ||
219 | 130 | |||
220 | 131 | |||
221 | 132 | def log_entry(): | ||
222 | 133 | log("<-- Entering {}".format(script_name())) | ||
223 | 134 | |||
224 | 135 | |||
225 | 136 | def log_exit(): | ||
226 | 137 | log("--> Exiting {}".format(script_name())) | ||
227 | 138 | |||
228 | 139 | |||
229 | 140 | class DictDiffer: | ||
230 | 141 | """ | ||
231 | 142 | Calculate the difference between two dictionaries as: | ||
232 | 143 | (1) items added | ||
233 | 144 | (2) items removed | ||
234 | 145 | (3) keys same in both but changed values | ||
235 | 146 | (4) keys same in both and unchanged values | ||
236 | 147 | """ | ||
237 | 148 | |||
238 | 149 | # Based on answer by hughdbrown at: | ||
239 | 150 | # http://stackoverflow.com/questions/1165352 | ||
240 | 151 | |||
241 | 152 | def __init__(self, current_dict, past_dict): | ||
242 | 153 | self.current_dict = current_dict | ||
243 | 154 | self.past_dict = past_dict | ||
244 | 155 | self.set_current = set(current_dict) | ||
245 | 156 | self.set_past = set(past_dict) | ||
246 | 157 | self.intersect = self.set_current.intersection(self.set_past) | ||
247 | 158 | |||
248 | 159 | @property | ||
249 | 160 | def added(self): | ||
250 | 161 | return self.set_current - self.intersect | ||
251 | 162 | |||
252 | 163 | @property | ||
253 | 164 | def removed(self): | ||
254 | 165 | return self.set_past - self.intersect | ||
255 | 166 | |||
256 | 167 | @property | ||
257 | 168 | def changed(self): | ||
258 | 169 | return set(key for key in self.intersect | ||
259 | 170 | if self.past_dict[key] != self.current_dict[key]) | ||
260 | 171 | @property | ||
261 | 172 | def unchanged(self): | ||
262 | 173 | return set(key for key in self.intersect | ||
263 | 174 | if self.past_dict[key] == self.current_dict[key]) | ||
264 | 175 | @property | ||
265 | 176 | def modified(self): | ||
266 | 177 | return self.current_dict != self.past_dict | ||
267 | 178 | |||
268 | 179 | @property | ||
269 | 180 | def added_or_changed(self): | ||
270 | 181 | return self.added.union(self.changed) | ||
271 | 182 | |||
272 | 183 | def _changes(self, keys): | ||
273 | 184 | new = {} | ||
274 | 185 | old = {} | ||
275 | 186 | for k in keys: | ||
276 | 187 | new[k] = self.current_dict.get(k) | ||
277 | 188 | old[k] = self.past_dict.get(k) | ||
278 | 189 | return "%s -> %s" % (old, new) | ||
279 | 190 | |||
280 | 191 | def __str__(self): | ||
281 | 192 | if self.modified: | ||
282 | 193 | s = dedent("""\ | ||
283 | 194 | added: %s | ||
284 | 195 | removed: %s | ||
285 | 196 | changed: %s | ||
286 | 197 | unchanged: %s""") % ( | ||
287 | 198 | self._changes(self.added), | ||
288 | 199 | self._changes(self.removed), | ||
289 | 200 | self._changes(self.changed), | ||
290 | 201 | list(self.unchanged)) | ||
291 | 202 | else: | ||
292 | 203 | s = "no changes" | ||
293 | 204 | return s | ||
294 | 205 | |||
295 | 206 | |||
296 | 207 | def make_charm_config_file(charm_config): | 60 | def make_charm_config_file(charm_config): |
297 | 208 | charm_config_file = tempfile.NamedTemporaryFile() | 61 | charm_config_file = tempfile.NamedTemporaryFile() |
298 | 209 | charm_config_file.write(yaml.dump(charm_config)) | 62 | charm_config_file.write(yaml.dump(charm_config)) |
299 | @@ -312,80 +165,3 @@ | |||
300 | 312 | if time.time() - start_time >= timeout: | 165 | if time.time() - start_time >= timeout: |
301 | 313 | raise RuntimeError('timeout waiting for contents of ' + url) | 166 | raise RuntimeError('timeout waiting for contents of ' + url) |
302 | 314 | time.sleep(0.1) | 167 | time.sleep(0.1) |
303 | 315 | |||
304 | 316 | |||
305 | 317 | class Serializer: | ||
306 | 318 | """Handle JSON (de)serialization.""" | ||
307 | 319 | |||
308 | 320 | def __init__(self, path, default=None, serialize=None, deserialize=None): | ||
309 | 321 | self.path = path | ||
310 | 322 | self.default = default or {} | ||
311 | 323 | self.serialize = serialize or json.dump | ||
312 | 324 | self.deserialize = deserialize or json.load | ||
313 | 325 | |||
314 | 326 | def exists(self): | ||
315 | 327 | return os.path.exists(self.path) | ||
316 | 328 | |||
317 | 329 | def get(self): | ||
318 | 330 | if self.exists(): | ||
319 | 331 | with open(self.path) as f: | ||
320 | 332 | return self.deserialize(f) | ||
321 | 333 | return self.default | ||
322 | 334 | |||
323 | 335 | def set(self, data): | ||
324 | 336 | with open(self.path, 'w') as f: | ||
325 | 337 | self.serialize(data, f) | ||
326 | 338 | |||
327 | 339 | |||
328 | 340 | @contextmanager | ||
329 | 341 | def cd(directory): | ||
330 | 342 | """A context manager to temporary change current working dir, e.g.:: | ||
331 | 343 | |||
332 | 344 | >>> import os | ||
333 | 345 | >>> os.chdir('/tmp') | ||
334 | 346 | >>> with cd('/bin'): print os.getcwd() | ||
335 | 347 | /bin | ||
336 | 348 | >>> os.getcwd() | ||
337 | 349 | '/tmp' | ||
338 | 350 | """ | ||
339 | 351 | cwd = os.getcwd() | ||
340 | 352 | os.chdir(directory) | ||
341 | 353 | yield | ||
342 | 354 | os.chdir(cwd) | ||
343 | 355 | |||
344 | 356 | |||
345 | 357 | def get_user_ids(user): | ||
346 | 358 | """Return the uid and gid of given `user`, e.g.:: | ||
347 | 359 | |||
348 | 360 | >>> get_user_ids('root') | ||
349 | 361 | (0, 0) | ||
350 | 362 | """ | ||
351 | 363 | userdata = pwd.getpwnam(user) | ||
352 | 364 | return userdata.pw_uid, userdata.pw_gid | ||
353 | 365 | |||
354 | 366 | |||
355 | 367 | def get_user_home(user): | ||
356 | 368 | """Return the home directory of the given `user`. | ||
357 | 369 | |||
358 | 370 | >>> get_user_home('root') | ||
359 | 371 | '/root' | ||
360 | 372 | """ | ||
361 | 373 | return pwd.getpwnam(user).pw_dir | ||
362 | 374 | |||
363 | 375 | |||
364 | 376 | @contextmanager | ||
365 | 377 | def su(user): | ||
366 | 378 | """A context manager to temporary run the script as a different user.""" | ||
367 | 379 | uid, gid = get_user_ids(user) | ||
368 | 380 | os.setegid(gid) | ||
369 | 381 | os.seteuid(uid) | ||
370 | 382 | current_home = os.getenv('HOME') | ||
371 | 383 | home = get_user_home(user) | ||
372 | 384 | os.environ['HOME'] = home | ||
373 | 385 | try: | ||
374 | 386 | yield Env(uid, gid, home) | ||
375 | 387 | finally: | ||
376 | 388 | os.setegid(os.getgid()) | ||
377 | 389 | os.seteuid(os.getuid()) | ||
378 | 390 | if current_home is not None: | ||
379 | 391 | os.environ['HOME'] = current_home | ||
380 | 392 | 168 | ||
381 | === modified file 'hooks/install' | |||
382 | --- hooks/install 2012-02-22 19:42:50 +0000 | |||
383 | +++ hooks/install 2012-02-29 22:49:25 +0000 | |||
384 | @@ -5,15 +5,75 @@ | |||
385 | 5 | 5 | ||
386 | 6 | import os | 6 | import os |
387 | 7 | import shutil | 7 | import shutil |
389 | 8 | from subprocess import CalledProcessError | 8 | import subprocess |
390 | 9 | |||
391 | 10 | |||
392 | 11 | def run(*args): | ||
393 | 12 | """Run the command with the given arguments. | ||
394 | 13 | |||
395 | 14 | The first argument is the path to the command to run, subsequent arguments | ||
396 | 15 | are command-line arguments to be passed. | ||
397 | 16 | """ | ||
398 | 17 | process = subprocess.Popen(args, stdout=subprocess.PIPE, | ||
399 | 18 | stderr=subprocess.PIPE, close_fds=True) | ||
400 | 19 | stdout, stderr = process.communicate() | ||
401 | 20 | if process.returncode: | ||
402 | 21 | raise subprocess.CalledProcessError( | ||
403 | 22 | process.returncode, repr(args), output=stdout+stderr) | ||
404 | 23 | return stdout | ||
405 | 24 | |||
406 | 25 | |||
407 | 26 | def command(*base_args): | ||
408 | 27 | """Return a callable that will run the given command with any arguments. | ||
409 | 28 | |||
410 | 29 | The first argument is the path to the command to run, subsequent arguments | ||
411 | 30 | are command-line arguments to "bake into" the returned callable. | ||
412 | 31 | |||
413 | 32 | The callable runs the given executable and also takes arguments that will | ||
414 | 33 | be appeneded to the "baked in" arguments. | ||
415 | 34 | |||
416 | 35 | For example, this code will list a file named "foo" (if it exists): | ||
417 | 36 | |||
418 | 37 | ls_foo = command('/bin/ls', 'foo') | ||
419 | 38 | ls_foo() | ||
420 | 39 | |||
421 | 40 | While this invocation will list "foo" and "bar" (assuming they exist): | ||
422 | 41 | |||
423 | 42 | ls_foo('bar') | ||
424 | 43 | """ | ||
425 | 44 | def callable_command(*args): | ||
426 | 45 | all_args = base_args + args | ||
427 | 46 | return run(*all_args) | ||
428 | 47 | |||
429 | 48 | return callable_command | ||
430 | 49 | |||
431 | 50 | |||
432 | 51 | log = command('juju-log') | ||
433 | 52 | |||
434 | 53 | |||
435 | 54 | def install_extra_repository(extra_repository): | ||
436 | 55 | try: | ||
437 | 56 | run('apt-add-repository', extra_repository) | ||
438 | 57 | run('apt-get', 'update') | ||
439 | 58 | except subprocess.CalledProcessError as e: | ||
440 | 59 | log('Error adding repository: ' + extra_repository) | ||
441 | 60 | log(e) | ||
442 | 61 | raise | ||
443 | 62 | |||
444 | 63 | |||
445 | 64 | def install_packages(): | ||
446 | 65 | apt_get_install = command('apt-get', 'install', '-y', '--force-yes') | ||
447 | 66 | apt_get_install('bzr', 'python-boto') | ||
448 | 67 | install_extra_repository('ppa:yellow/ppa') | ||
449 | 68 | apt_get_install('python-shell-toolbox') | ||
450 | 69 | |||
451 | 70 | |||
452 | 71 | install_packages() | ||
453 | 9 | 72 | ||
454 | 10 | from helpers import ( | 73 | from helpers import ( |
455 | 11 | apt_get_install, | ||
456 | 12 | get_config, | 74 | get_config, |
457 | 13 | log, | ||
458 | 14 | log_entry, | 75 | log_entry, |
459 | 15 | log_exit, | 76 | log_exit, |
460 | 16 | run, | ||
461 | 17 | ) | 77 | ) |
462 | 18 | from local import ( | 78 | from local import ( |
463 | 19 | config_json, | 79 | config_json, |
464 | @@ -22,13 +82,12 @@ | |||
465 | 22 | 82 | ||
466 | 23 | 83 | ||
467 | 24 | def cleanup(buildbot_dir): | 84 | def cleanup(buildbot_dir): |
468 | 25 | apt_get_install('bzr', 'python-boto') | ||
469 | 26 | # Since we may be installing into a pre-existing service, ensure the | 85 | # Since we may be installing into a pre-existing service, ensure the |
470 | 27 | # buildbot directory is removed. | 86 | # buildbot directory is removed. |
471 | 28 | if os.path.exists(buildbot_dir): | 87 | if os.path.exists(buildbot_dir): |
472 | 29 | try: | 88 | try: |
473 | 30 | run('buildbot', 'stop', buildbot_dir) | 89 | run('buildbot', 'stop', buildbot_dir) |
475 | 31 | except (CalledProcessError, OSError): | 90 | except (subprocess.CalledProcessError, OSError): |
476 | 32 | # This usually happens because buildbot hasn't been | 91 | # This usually happens because buildbot hasn't been |
477 | 33 | # installed yet, or that it wasn't running; just | 92 | # installed yet, or that it wasn't running; just |
478 | 34 | # ignore the error. | 93 | # ignore the error. |
479 | @@ -52,6 +111,7 @@ | |||
480 | 52 | 111 | ||
481 | 53 | 112 | ||
482 | 54 | if __name__ == '__main__': | 113 | if __name__ == '__main__': |
483 | 114 | |||
484 | 55 | log_entry() | 115 | log_entry() |
485 | 56 | try: | 116 | try: |
486 | 57 | main() | 117 | main() |
487 | 58 | 118 | ||
488 | === modified file 'hooks/local.py' | |||
489 | --- hooks/local.py 2012-02-23 21:50:41 +0000 | |||
490 | +++ hooks/local.py 2012-02-29 22:49:25 +0000 | |||
491 | @@ -26,14 +26,16 @@ | |||
492 | 26 | import subprocess | 26 | import subprocess |
493 | 27 | import uuid | 27 | import uuid |
494 | 28 | 28 | ||
496 | 29 | from helpers import ( | 29 | from shelltoolbox import ( |
497 | 30 | cd, | 30 | cd, |
498 | 31 | get_config, | ||
499 | 32 | log, | ||
500 | 33 | run, | 31 | run, |
501 | 34 | Serializer, | 32 | Serializer, |
502 | 35 | su, | 33 | su, |
503 | 36 | ) | 34 | ) |
504 | 35 | from helpers import ( | ||
505 | 36 | get_config, | ||
506 | 37 | log, | ||
507 | 38 | ) | ||
508 | 37 | 39 | ||
509 | 38 | 40 | ||
510 | 39 | HTTP_PORT_PROTOCOL = '8010/TCP' | 41 | HTTP_PORT_PROTOCOL = '8010/TCP' |
511 | 40 | 42 | ||
512 | === modified file 'hooks/start' | |||
513 | --- hooks/start 2012-02-22 19:42:50 +0000 | |||
514 | +++ hooks/start 2012-02-29 22:49:25 +0000 | |||
515 | @@ -3,10 +3,11 @@ | |||
516 | 3 | # Copyright 2012 Canonical Ltd. This software is licensed under the | 3 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
517 | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). |
518 | 5 | 5 | ||
519 | 6 | from shelltoolbox import run | ||
520 | 7 | |||
521 | 6 | from helpers import ( | 8 | from helpers import ( |
522 | 7 | log_entry, | 9 | log_entry, |
523 | 8 | log_exit, | 10 | log_exit, |
524 | 9 | run, | ||
525 | 10 | ) | 11 | ) |
526 | 11 | from local import HTTP_PORT_PROTOCOL | 12 | from local import HTTP_PORT_PROTOCOL |
527 | 12 | 13 | ||
528 | 13 | 14 | ||
529 | === modified file 'hooks/stop' | |||
530 | --- hooks/stop 2012-02-22 19:42:50 +0000 | |||
531 | +++ hooks/stop 2012-02-29 22:49:25 +0000 | |||
532 | @@ -3,12 +3,13 @@ | |||
533 | 3 | # Copyright 2012 Canonical Ltd. This software is licensed under the | 3 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
534 | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). | 4 | # GNU Affero General Public License version 3 (see the file LICENSE). |
535 | 5 | 5 | ||
536 | 6 | from shelltoolbox import run | ||
537 | 7 | |||
538 | 6 | from helpers import ( | 8 | from helpers import ( |
540 | 7 | get_config, | 9 | get_config |
541 | 8 | log, | 10 | log, |
542 | 9 | log_entry, | 11 | log_entry, |
543 | 10 | log_exit, | 12 | log_exit, |
544 | 11 | run, | ||
545 | 12 | ) | 13 | ) |
546 | 13 | from local import ( | 14 | from local import ( |
547 | 14 | HTTP_PORT_PROTOCOL, | 15 | HTTP_PORT_PROTOCOL, |
548 | 15 | 16 | ||
549 | === modified file 'hooks/tests.py' | |||
550 | --- hooks/tests.py 2012-02-23 16:45:54 +0000 | |||
551 | +++ hooks/tests.py 2012-02-29 22:49:25 +0000 | |||
552 | @@ -12,14 +12,14 @@ | |||
553 | 12 | import tempfile | 12 | import tempfile |
554 | 13 | import unittest | 13 | import unittest |
555 | 14 | 14 | ||
557 | 15 | from helpers import ( | 15 | from shelltoolbox import ( |
558 | 16 | cd, | 16 | cd, |
559 | 17 | command, | 17 | command, |
560 | 18 | DictDiffer, | 18 | DictDiffer, |
561 | 19 | run, | 19 | run, |
562 | 20 | su, | 20 | su, |
563 | 21 | unit_info, | ||
564 | 22 | ) | 21 | ) |
565 | 22 | from helpers import unit_info | ||
566 | 23 | from local import ( | 23 | from local import ( |
567 | 24 | get_bucket, | 24 | get_bucket, |
568 | 25 | get_key, | 25 | get_key, |
569 | @@ -28,95 +28,6 @@ | |||
570 | 28 | ) | 28 | ) |
571 | 29 | 29 | ||
572 | 30 | 30 | ||
573 | 31 | class TestRun(unittest.TestCase): | ||
574 | 32 | |||
575 | 33 | def testSimpleCommand(self): | ||
576 | 34 | # Running a simple command (ls) works and running the command | ||
577 | 35 | # produces a string. | ||
578 | 36 | self.assertIsInstance(run('/bin/ls'), str) | ||
579 | 37 | |||
580 | 38 | def testStdoutReturned(self): | ||
581 | 39 | # Running a simple command (ls) works and running the command | ||
582 | 40 | # produces a string. | ||
583 | 41 | self.assertIn('Usage:', run('/bin/ls', '--help')) | ||
584 | 42 | |||
585 | 43 | def testCalledProcessErrorRaised(self): | ||
586 | 44 | # If an error occurs a CalledProcessError is raised with the return | ||
587 | 45 | # code, command executed, and the output of the command. | ||
588 | 46 | with self.assertRaises(CalledProcessError) as info: | ||
589 | 47 | run('ls', '--not a valid switch') | ||
590 | 48 | exception = info.exception | ||
591 | 49 | self.assertEqual(2, exception.returncode) | ||
592 | 50 | self.assertEqual("('ls', '--not a valid switch')", exception.cmd) | ||
593 | 51 | self.assertIn('unrecognized option', exception.output) | ||
594 | 52 | |||
595 | 53 | |||
596 | 54 | class TestCommand(unittest.TestCase): | ||
597 | 55 | |||
598 | 56 | def testSimpleCommand(self): | ||
599 | 57 | # Creating a simple command (ls) works and running the command | ||
600 | 58 | # produces a string. | ||
601 | 59 | ls = command('/bin/ls') | ||
602 | 60 | self.assertIsInstance(ls(), str) | ||
603 | 61 | |||
604 | 62 | def testArguments(self): | ||
605 | 63 | # Arguments can be passed to commands. | ||
606 | 64 | ls = command('/bin/ls') | ||
607 | 65 | self.assertIn('Usage:', ls('--help')) | ||
608 | 66 | |||
609 | 67 | def testMissingExecutable(self): | ||
610 | 68 | # If the command does not exist, an OSError (No such file or | ||
611 | 69 | # directory) is raised. | ||
612 | 70 | bad = command('this command does not exist') | ||
613 | 71 | with self.assertRaises(OSError) as info: | ||
614 | 72 | bad() | ||
615 | 73 | self.assertEqual(2, info.exception.errno) | ||
616 | 74 | |||
617 | 75 | def testError(self): | ||
618 | 76 | # If the command returns a non-zero exit code, an exception is raised. | ||
619 | 77 | ls = command('/bin/ls') | ||
620 | 78 | with self.assertRaises(CalledProcessError): | ||
621 | 79 | ls('--not a valid switch') | ||
622 | 80 | |||
623 | 81 | def testBakedInArguments(self): | ||
624 | 82 | # Arguments can be passed when creating the command as well as when | ||
625 | 83 | # executing it. | ||
626 | 84 | ll = command('/bin/ls', '-l') | ||
627 | 85 | self.assertIn('rw', ll()) # Assumes a file is r/w in the pwd. | ||
628 | 86 | self.assertIn('Usage:', ll('--help')) | ||
629 | 87 | |||
630 | 88 | def testQuoting(self): | ||
631 | 89 | # There is no need to quote special shell characters in commands. | ||
632 | 90 | ls = command('/bin/ls') | ||
633 | 91 | ls('--help', '>') | ||
634 | 92 | |||
635 | 93 | |||
636 | 94 | class TestDictDiffer(unittest.TestCase): | ||
637 | 95 | |||
638 | 96 | def testStr(self): | ||
639 | 97 | a = dict(cow='moo', pig='oink') | ||
640 | 98 | b = dict(cow='moo', pig='oinkoink', horse='nay') | ||
641 | 99 | diff = DictDiffer(b, a) | ||
642 | 100 | s = str(diff) | ||
643 | 101 | self.assertIn("added: {'horse': None} -> {'horse': 'nay'}", s) | ||
644 | 102 | self.assertIn("removed: {} -> {}", s) | ||
645 | 103 | self.assertIn("changed: {'pig': 'oink'} -> {'pig': 'oinkoink'}", s) | ||
646 | 104 | self.assertIn("unchanged: ['cow']", s) | ||
647 | 105 | |||
648 | 106 | def testStrUnmodified(self): | ||
649 | 107 | a = dict(cow='moo', pig='oink') | ||
650 | 108 | diff = DictDiffer(a, a) | ||
651 | 109 | s = str(diff) | ||
652 | 110 | self.assertEquals('no changes', s) | ||
653 | 111 | |||
654 | 112 | def testAddedOrChanged(self): | ||
655 | 113 | a = dict(cow='moo', pig='oink') | ||
656 | 114 | b = dict(cow='moo', pig='oinkoink', horse='nay') | ||
657 | 115 | diff = DictDiffer(b, a) | ||
658 | 116 | expected = set(['horse', 'pig']) | ||
659 | 117 | self.assertEquals(expected, diff.added_or_changed) | ||
660 | 118 | |||
661 | 119 | |||
662 | 120 | class TestUnit_info(unittest.TestCase): | 31 | class TestUnit_info(unittest.TestCase): |
663 | 121 | 32 | ||
664 | 122 | def make_data(self, state='started'): | 33 | def make_data(self, state='started'): |
665 | @@ -153,83 +64,6 @@ | |||
666 | 153 | self.assertNotEqual('started', state) | 64 | self.assertNotEqual('started', state) |
667 | 154 | 65 | ||
668 | 155 | 66 | ||
669 | 156 | current_euid = os.geteuid() | ||
670 | 157 | current_egid = os.getegid() | ||
671 | 158 | current_home = os.environ['HOME'] | ||
672 | 159 | example_euid = current_euid + 1 | ||
673 | 160 | example_egid = current_egid + 1 | ||
674 | 161 | example_home = '/var/lib/example' | ||
675 | 162 | userinfo = {'example_user': dict( | ||
676 | 163 | ids=(example_euid, example_egid), home=example_home)} | ||
677 | 164 | effective_values = dict(uid=current_euid, gid=current_egid) | ||
678 | 165 | |||
679 | 166 | |||
680 | 167 | def stub_os_seteuid(value): | ||
681 | 168 | effective_values['uid'] = value | ||
682 | 169 | |||
683 | 170 | |||
684 | 171 | def stub_os_setegid(value): | ||
685 | 172 | effective_values['gid'] = value | ||
686 | 173 | |||
687 | 174 | |||
688 | 175 | class TestSuContextManager(unittest.TestCase): | ||
689 | 176 | |||
690 | 177 | def setUp(self): | ||
691 | 178 | import helpers | ||
692 | 179 | self.os_seteuid = os.seteuid | ||
693 | 180 | self.os_setegid = os.setegid | ||
694 | 181 | self.helpers_get_user_ids = helpers.get_user_ids | ||
695 | 182 | self.helpers_get_user_home = helpers.get_user_home | ||
696 | 183 | os.seteuid = stub_os_seteuid | ||
697 | 184 | os.setegid = stub_os_setegid | ||
698 | 185 | helpers.get_user_ids = lambda user: userinfo[user]['ids'] | ||
699 | 186 | helpers.get_user_home = lambda user: userinfo[user]['home'] | ||
700 | 187 | |||
701 | 188 | def tearDown(self): | ||
702 | 189 | import helpers | ||
703 | 190 | os.seteuid = self.os_seteuid | ||
704 | 191 | os.setegid = self.os_setegid | ||
705 | 192 | helpers.get_user_ids = self.helpers_get_user_ids | ||
706 | 193 | helpers.get_user_home = self.helpers_get_user_home | ||
707 | 194 | |||
708 | 195 | def testChange(self): | ||
709 | 196 | with su('example_user'): | ||
710 | 197 | self.assertEqual(example_euid, effective_values['uid']) | ||
711 | 198 | self.assertEqual(example_egid, effective_values['gid']) | ||
712 | 199 | self.assertEqual(example_home, os.environ['HOME']) | ||
713 | 200 | |||
714 | 201 | def testEnvironment(self): | ||
715 | 202 | with su('example_user') as e: | ||
716 | 203 | self.assertEqual(example_euid, e.uid) | ||
717 | 204 | self.assertEqual(example_egid, e.gid) | ||
718 | 205 | self.assertEqual(example_home, e.home) | ||
719 | 206 | |||
720 | 207 | def testRevert(self): | ||
721 | 208 | with su('example_user'): | ||
722 | 209 | pass | ||
723 | 210 | self.assertEqual(current_euid, effective_values['uid']) | ||
724 | 211 | self.assertEqual(current_egid, effective_values['gid']) | ||
725 | 212 | self.assertEqual(current_home, os.environ['HOME']) | ||
726 | 213 | |||
727 | 214 | def testRevertAfterFailure(self): | ||
728 | 215 | try: | ||
729 | 216 | with su('example_user'): | ||
730 | 217 | raise RuntimeError() | ||
731 | 218 | except RuntimeError: | ||
732 | 219 | self.assertEqual(current_euid, effective_values['uid']) | ||
733 | 220 | self.assertEqual(current_egid, effective_values['gid']) | ||
734 | 221 | self.assertEqual(current_home, os.environ['HOME']) | ||
735 | 222 | |||
736 | 223 | |||
737 | 224 | class TestCdContextManager(unittest.TestCase): | ||
738 | 225 | def test_cd(self): | ||
739 | 226 | curdir = os.getcwd() | ||
740 | 227 | self.assertNotEqual('/var', curdir) | ||
741 | 228 | with cd('/var'): | ||
742 | 229 | self.assertEqual('/var', os.getcwd()) | ||
743 | 230 | self.assertEqual(curdir, os.getcwd()) | ||
744 | 231 | |||
745 | 232 | |||
746 | 233 | class StubBucket: | 67 | class StubBucket: |
747 | 234 | """Stub S3 Bucket.""" | 68 | """Stub S3 Bucket.""" |
748 | 235 | def __init__(self, name): | 69 | def __init__(self, name): |
Approved based on conversation with Brad on G+.