OLD | NEW |
1 """Juju GUI charm utilities.""" | 1 """Juju GUI charm utilities.""" |
2 | 2 |
3 __all__ = [ | 3 __all__ = [ |
4 'AGENT', | 4 'AGENT', |
5 'API_PORT', | 5 'API_PORT', |
| 6 'CURRENT_DIR', |
| 7 'HAPROXY', |
| 8 'IMPROV', |
| 9 'JUJU_DIR', |
| 10 'JUJU_GUI_DIR', |
| 11 'JUJU_GUI_SITE', |
| 12 'JUJU_PEM', |
| 13 'NGINX', |
| 14 'StopChain', |
| 15 'WEB_PORT', |
6 'bzr_checkout', | 16 'bzr_checkout', |
| 17 'chain', |
7 'cmd_log', | 18 'cmd_log', |
8 'CURRENT_DIR', | |
9 'fetch_api', | 19 'fetch_api', |
10 'fetch_gui', | 20 'fetch_gui', |
11 'first_path_in_dir', | 21 'first_path_in_dir', |
12 'get_api_address', | 22 'get_api_address', |
13 'get_release_file_url', | 23 'get_release_file_url', |
14 'get_staging_dependencies', | 24 'get_staging_dependencies', |
15 'get_zookeeper_address', | 25 'get_zookeeper_address', |
16 'HAPROXY', | |
17 'IMPROV', | |
18 'JUJU_DIR', | |
19 'JUJU_GUI_DIR', | |
20 'JUJU_GUI_SITE', | |
21 'JUJU_PEM', | |
22 'legacy_juju', | 26 'legacy_juju', |
23 'log_hook', | 27 'log_hook', |
24 'NGINX', | 28 'merge', |
| 29 'overrideable', |
25 'parse_source', | 30 'parse_source', |
26 'render_to_file', | 31 'render_to_file', |
27 'save_or_create_certificates', | 32 'save_or_create_certificates', |
28 'setup_gui', | 33 'setup_gui', |
29 'setup_nginx', | 34 'setup_nginx', |
30 'start_agent', | 35 'start_agent', |
31 'start_gui', | 36 'start_gui', |
32 'start_improv', | 37 'start_improv', |
33 'stop', | |
34 'WEB_PORT', | |
35 ] | 38 ] |
36 | 39 |
37 from contextlib import contextmanager | 40 from contextlib import contextmanager |
38 import json | 41 import json |
39 import os | 42 import os |
40 import logging | 43 import logging |
41 import shutil | 44 import shutil |
42 from subprocess import CalledProcessError | 45 from subprocess import CalledProcessError |
43 import tempfile | 46 import tempfile |
44 | 47 |
45 from launchpadlib.launchpad import Launchpad | 48 from launchpadlib.launchpad import Launchpad |
46 from shelltoolbox import ( | 49 from shelltoolbox import ( |
47 apt_get_install, | 50 apt_get_install, |
48 command, | 51 command, |
49 environ, | 52 environ, |
50 install_extra_repositories, | 53 install_extra_repositories, |
51 run, | 54 run, |
52 script_name, | 55 script_name, |
53 search_file, | 56 search_file, |
54 Serializer, | 57 Serializer, |
55 su, | 58 su, |
56 ) | 59 ) |
57 from charmhelpers import ( | 60 from charmhelpers import ( |
58 get_config, | 61 get_config, |
59 log, | 62 log, |
60 service_control, | 63 service_control, |
| 64 RESTART, |
61 START, | 65 START, |
62 STOP, | |
63 unit_get, | 66 unit_get, |
64 ) | 67 ) |
| 68 |
| 69 import apt |
65 import tempita | 70 import tempita |
66 | 71 |
67 | 72 |
68 AGENT = 'juju-api-agent' | 73 AGENT = 'juju-api-agent' |
69 IMPROV = 'juju-api-improv' | 74 IMPROV = 'juju-api-improv' |
70 HAPROXY = 'haproxy' | 75 HAPROXY = 'haproxy' |
71 NGINX = 'nginx' | 76 NGINX = 'nginx' |
72 | 77 |
73 API_PORT = 8080 | 78 API_PORT = 8080 |
74 WEB_PORT = 8000 | 79 WEB_PORT = 8000 |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 """Parse the ``juju-gui-source`` option. | 211 """Parse the ``juju-gui-source`` option. |
207 | 212 |
208 Return a tuple of two elements representing info on how to deploy Juju GUI. | 213 Return a tuple of two elements representing info on how to deploy Juju GUI. |
209 Examples: | 214 Examples: |
210 - ('stable', None): latest stable release; | 215 - ('stable', None): latest stable release; |
211 - ('stable', '0.1.0'): stable release v0.1.0; | 216 - ('stable', '0.1.0'): stable release v0.1.0; |
212 - ('trunk', None): latest trunk release; | 217 - ('trunk', None): latest trunk release; |
213 - ('trunk', '0.1.0+build.1'): trunk release v0.1.0 bzr revision 1; | 218 - ('trunk', '0.1.0+build.1'): trunk release v0.1.0 bzr revision 1; |
214 - ('branch', 'lp:juju-gui'): release is made from a branch. | 219 - ('branch', 'lp:juju-gui'): release is made from a branch. |
215 """ | 220 """ |
| 221 if source.startswith('url:'): |
| 222 return 'url', source[4:] |
216 if source in ('stable', 'trunk'): | 223 if source in ('stable', 'trunk'): |
217 return source, None | 224 return source, None |
218 if source.startswith('lp:') or source.startswith('http://'): | 225 if source.startswith('lp:') or source.startswith('http://'): |
219 return 'branch', source | 226 return 'branch', source |
220 if 'build' in source: | 227 if 'build' in source: |
221 return 'trunk', source | 228 return 'trunk', source |
222 return 'stable', source | 229 return 'stable', source |
223 | 230 |
224 | 231 |
225 def render_to_file(template_name, context, destination): | 232 def render_to_file(template_name, context, destination): |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
296 } | 303 } |
297 render_to_file('juju-api-agent.conf.template', context, config_path) | 304 render_to_file('juju-api-agent.conf.template', context, config_path) |
298 log('Starting API agent.') | 305 log('Starting API agent.') |
299 with su('root'): | 306 with su('root'): |
300 service_control(AGENT, START) | 307 service_control(AGENT, START) |
301 | 308 |
302 | 309 |
303 def start_gui( | 310 def start_gui( |
304 console_enabled, login_help, readonly, in_staging, ssl_cert_path, | 311 console_enabled, login_help, readonly, in_staging, ssl_cert_path, |
305 serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg', | 312 serve_tests, haproxy_path='/etc/haproxy/haproxy.cfg', |
306 nginx_path=JUJU_GUI_SITE, config_js_path=None, secure=True): | 313 nginx_path=JUJU_GUI_SITE, config_js_path=None, secure=True, |
| 314 sandbox=False): |
307 """Set up and start the Juju GUI server.""" | 315 """Set up and start the Juju GUI server.""" |
308 with su('root'): | 316 with su('root'): |
309 run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR) | 317 run('chown', '-R', 'ubuntu:', JUJU_GUI_DIR) |
310 # XXX 2013-02-05 frankban bug=1116320: | 318 # XXX 2013-02-05 frankban bug=1116320: |
311 # External insecure resources are still loaded when testing in the | 319 # External insecure resources are still loaded when testing in the |
312 # debug environment. For now, switch to the production environment if | 320 # debug environment. For now, switch to the production environment if |
313 # the charm is configured to serve tests. | 321 # the charm is configured to serve tests. |
314 if in_staging and not serve_tests: | 322 if in_staging and not serve_tests: |
315 build_dirname = 'build-debug' | 323 build_dirname = 'build-debug' |
316 else: | 324 else: |
317 build_dirname = 'build-prod' | 325 build_dirname = 'build-prod' |
318 build_dir = os.path.join(JUJU_GUI_DIR, build_dirname) | 326 build_dir = os.path.join(JUJU_GUI_DIR, build_dirname) |
319 log('Generating the Juju GUI configuration file.') | 327 log('Generating the Juju GUI configuration file.') |
320 is_legacy_juju = legacy_juju() | 328 is_legacy_juju = legacy_juju() |
321 user, password = None, None | 329 user, password = None, None |
322 if is_legacy_juju and in_staging: | 330 if is_legacy_juju and in_staging: |
323 user, password = 'admin', 'admin' | 331 user, password = 'admin', 'admin' |
324 else: | 332 else: |
325 user, password = None, None | 333 user, password = None, None |
| 334 |
326 api_backend = 'python' if is_legacy_juju else 'go' | 335 api_backend = 'python' if is_legacy_juju else 'go' |
327 if secure: | 336 if secure: |
328 protocol = 'wss' | 337 protocol = 'wss' |
329 else: | 338 else: |
330 log('Running in insecure mode! Port 80 will serve unencrypted.') | 339 log('Running in insecure mode! Port 80 will serve unencrypted.') |
331 protocol = 'ws' | 340 protocol = 'ws' |
| 341 |
332 context = { | 342 context = { |
333 'raw_protocol': protocol, | 343 'raw_protocol': protocol, |
334 'address': unit_get('public-address'), | 344 'address': unit_get('public-address'), |
335 'console_enabled': json.dumps(console_enabled), | 345 'console_enabled': json.dumps(console_enabled), |
336 'login_help': json.dumps(login_help), | 346 'login_help': json.dumps(login_help), |
337 'password': json.dumps(password), | 347 'password': json.dumps(password), |
338 'api_backend': json.dumps(api_backend), | 348 'api_backend': json.dumps(api_backend), |
339 'readonly': json.dumps(readonly), | 349 'readonly': json.dumps(readonly), |
340 'user': json.dumps(user), | 350 'user': json.dumps(user), |
341 'protocol': json.dumps(protocol) | 351 'protocol': json.dumps(protocol), |
| 352 'sandbox': json.dumps(sandbox), |
342 } | 353 } |
343 if config_js_path is None: | 354 if config_js_path is None: |
344 config_js_path = os.path.join( | 355 config_js_path = os.path.join( |
345 build_dir, 'juju-ui', 'assets', 'config.js') | 356 build_dir, 'juju-ui', 'assets', 'config.js') |
346 render_to_file('config.js.template', context, config_js_path) | 357 render_to_file('config.js.template', context, config_js_path) |
347 log('Generating the nginx site configuration file.') | 358 log('Generating the nginx site configuration file.') |
348 context = { | 359 context = { |
349 'port': WEB_PORT, | 360 'port': WEB_PORT, |
350 'serve_tests': serve_tests, | 361 'serve_tests': serve_tests, |
351 'server_root': build_dir, | 362 'server_root': build_dir, |
(...skipping 16 matching lines...) Expand all Loading... |
368 # WebSocket connections. In juju-core the system already has the proper | 379 # WebSocket connections. In juju-core the system already has the proper |
369 # certificate installed. | 380 # certificate installed. |
370 'web_pem': JUJU_PEM, | 381 'web_pem': JUJU_PEM, |
371 'web_port': WEB_PORT, | 382 'web_port': WEB_PORT, |
372 'secure': secure | 383 'secure': secure |
373 } | 384 } |
374 render_to_file('haproxy.cfg.template', context, haproxy_path) | 385 render_to_file('haproxy.cfg.template', context, haproxy_path) |
375 log('Starting Juju GUI.') | 386 log('Starting Juju GUI.') |
376 with su('root'): | 387 with su('root'): |
377 # Start the Juju GUI. | 388 # Start the Juju GUI. |
378 service_control(NGINX, START) | 389 service_control(NGINX, RESTART) |
379 service_control(HAPROXY, START) | 390 service_control(HAPROXY, RESTART) |
380 | |
381 | |
382 def stop(in_staging): | |
383 """Stop the Juju API agent.""" | |
384 with su('root'): | |
385 log('Stopping Juju GUI.') | |
386 service_control(HAPROXY, STOP) | |
387 service_control(NGINX, STOP) | |
388 # No need to stop staging or the API agent in juju-core environments. | |
389 if legacy_juju(): | |
390 if in_staging: | |
391 log('Stopping the staging backend.') | |
392 service_control(IMPROV, STOP) | |
393 else: | |
394 log('Stopping API agent.') | |
395 service_control(AGENT, STOP) | |
396 | 391 |
397 | 392 |
398 def fetch_gui(juju_gui_source, logpath): | 393 def fetch_gui(juju_gui_source, logpath): |
399 """Retrieve the Juju GUI release/branch.""" | 394 """Retrieve the Juju GUI release/branch.""" |
400 # Retrieve a Juju GUI release. | 395 # Retrieve a Juju GUI release. |
401 origin, version_or_branch = parse_source(juju_gui_source) | 396 origin, version_or_branch = parse_source(juju_gui_source) |
402 if origin == 'branch': | 397 if origin == 'branch': |
403 # Make sure we have the dependencies necessary for us to actually make | 398 # Make sure we have the dependencies necessary for us to actually make |
404 # a build. | 399 # a build. |
405 _get_build_dependencies() | 400 _get_build_dependencies() |
406 # Create a release starting from a branch. | 401 # Create a release starting from a branch. |
407 juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source') | 402 juju_gui_source_dir = os.path.join(CURRENT_DIR, 'juju-gui-source') |
408 log('Retrieving Juju GUI source checkout from %s.' % version_or_branch) | 403 log('Retrieving Juju GUI source checkout from %s.' % version_or_branch) |
409 cmd_log(run('rm', '-rf', juju_gui_source_dir)) | 404 cmd_log(run('rm', '-rf', juju_gui_source_dir)) |
410 cmd_log(bzr_checkout(version_or_branch, juju_gui_source_dir)) | 405 cmd_log(bzr_checkout(version_or_branch, juju_gui_source_dir)) |
411 log('Preparing a Juju GUI release.') | 406 log('Preparing a Juju GUI release.') |
412 logdir = os.path.dirname(logpath) | 407 logdir = os.path.dirname(logpath) |
413 fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir) | 408 fd, name = tempfile.mkstemp(prefix='make-distfile-', dir=logdir) |
414 log('Output from "make distfile" sent to %s' % name) | 409 log('Output from "make distfile" sent to %s' % name) |
415 with environ(NO_BZR='1'): | 410 with environ(NO_BZR='1'): |
416 run('make', '-C', juju_gui_source_dir, 'distfile', | 411 run('make', '-C', juju_gui_source_dir, 'distfile', |
417 stdout=fd, stderr=fd) | 412 stdout=fd, stderr=fd) |
418 release_tarball = first_path_in_dir( | 413 release_tarball = first_path_in_dir( |
419 os.path.join(juju_gui_source_dir, 'releases')) | 414 os.path.join(juju_gui_source_dir, 'releases')) |
420 else: | 415 else: |
421 # Retrieve a release from Launchpad. | |
422 log('Retrieving Juju GUI release.') | 416 log('Retrieving Juju GUI release.') |
423 launchpad = Launchpad.login_anonymously('Juju GUI charm', 'production') | 417 if origin == 'url': |
424 project = launchpad.projects['juju-gui'] | 418 file_url = version_or_branch |
425 file_url = get_release_file_url(project, origin, version_or_branch) | 419 else: |
| 420 # Retrieve a release from Launchpad. |
| 421 launchpad = Launchpad.login_anonymously('Juju GUI charm', 'productio
n') |
| 422 project = launchpad.projects['juju-gui'] |
| 423 file_url = get_release_file_url(project, origin, version_or_branch) |
426 log('Downloading release file from %s.' % file_url) | 424 log('Downloading release file from %s.' % file_url) |
427 release_tarball = os.path.join(CURRENT_DIR, 'release.tgz') | 425 release_tarball = os.path.join(CURRENT_DIR, 'release.tgz') |
428 cmd_log(run('curl', '-L', '-o', release_tarball, file_url)) | 426 cmd_log(run('curl', '-L', '-o', release_tarball, file_url)) |
429 return release_tarball | 427 return release_tarball |
430 | 428 |
431 | 429 |
432 def fetch_api(juju_api_branch): | 430 def fetch_api(juju_api_branch): |
433 """Retrieve the Juju branch.""" | 431 """Retrieve the Juju branch.""" |
434 # Retrieve Juju API source checkout. | 432 # Retrieve Juju API source checkout. |
435 log('Retrieving Juju API source checkout.') | 433 log('Retrieving Juju API source checkout.') |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
493 # These are arbitrary test values for the certificate. | 491 # These are arbitrary test values for the certificate. |
494 '/C=GB/ST=Juju/L=GUI/O=Ubuntu/CN=juju.ubuntu.com', | 492 '/C=GB/ST=Juju/L=GUI/O=Ubuntu/CN=juju.ubuntu.com', |
495 '-keyout', key_path, '-out', crt_path)) | 493 '-keyout', key_path, '-out', crt_path)) |
496 # Generate the pem file. | 494 # Generate the pem file. |
497 pem_path = os.path.join(ssl_cert_path, JUJU_PEM) | 495 pem_path = os.path.join(ssl_cert_path, JUJU_PEM) |
498 if os.path.exists(pem_path): | 496 if os.path.exists(pem_path): |
499 os.remove(pem_path) | 497 os.remove(pem_path) |
500 with open(pem_path, 'w') as pem_file: | 498 with open(pem_path, 'w') as pem_file: |
501 shutil.copyfileobj(open(key_path), pem_file) | 499 shutil.copyfileobj(open(key_path), pem_file) |
502 shutil.copyfileobj(open(crt_path), pem_file) | 500 shutil.copyfileobj(open(crt_path), pem_file) |
| 501 |
| 502 |
| 503 def check_packages(*packages): |
| 504 """Given a list of packages, return the packages which are not installed. |
| 505 """ |
| 506 cache = apt.Cache() |
| 507 missing = set() |
| 508 for pkg_name in packages: |
| 509 try: |
| 510 pkg = cache[pkg_name] |
| 511 except KeyError: |
| 512 missing.add(pkg_name) |
| 513 continue |
| 514 if pkg.is_installed: |
| 515 continue |
| 516 missing.add(pkg_name) |
| 517 return missing |
| 518 |
| 519 |
| 520 ## Backend support decorators |
| 521 class StopChain(Exception): |
| 522 """Stop Processing a chain command without raising |
| 523 another error. |
| 524 """ |
| 525 |
| 526 def chain(name, reverse=False): |
| 527 """Helper method to compose a set of strategy objects into |
| 528 a callable. |
| 529 |
| 530 Each method is called in the context of its strategy |
| 531 instance (normal OOP) and its argument is the Backend |
| 532 instance. |
| 533 """ |
| 534 # chain method calls through all implementing mixins |
| 535 def method(self): |
| 536 workingset = self.backends |
| 537 if reverse: |
| 538 workingset = reversed(workingset) |
| 539 for backend in workingset: |
| 540 call = backend.__class__.__dict__.get(name) |
| 541 if call: |
| 542 try: |
| 543 call(backend, self) |
| 544 except StopChain: |
| 545 break |
| 546 |
| 547 |
| 548 method.__name__ = name |
| 549 return method |
| 550 |
| 551 def overrideable(f): |
| 552 """Helper to support very limited overrides for use in testing. |
| 553 |
| 554 def foo(): |
| 555 return True |
| 556 b = Backend(foo=foo) |
| 557 assert b.foo() is True |
| 558 """ |
| 559 name = f.__name__ |
| 560 def overridden(self, *args, **kwargs): |
| 561 if name in self.overrides: |
| 562 return self.overrides[name](*args, **kwargs) |
| 563 else: |
| 564 return f(self, *args, **kwargs) |
| 565 overridden.__name__ = name |
| 566 return overridden |
| 567 |
| 568 def merge(name): |
| 569 """Helper to merge a property from a set of strategy objects |
| 570 into a unified set. |
| 571 """ |
| 572 # return merged property from every providing backend as a set |
| 573 @property |
| 574 def method(self): |
| 575 result = set() |
| 576 for backend in self.backends: |
| 577 segment = backend.__class__.__dict__.get(name) |
| 578 if segment and isinstance(segment, (list, tuple, set)): |
| 579 result |= set(segment) |
| 580 |
| 581 return result |
| 582 return method |
| 583 |
| 584 |
| 585 |
OLD | NEW |