Merge lp:~frankban/juju-quickstart/open-urls into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 9
Proposed branch: lp:~frankban/juju-quickstart/open-urls
Merge into: lp:juju-quickstart
Diff against target: 445 lines (+220/-35)
7 files modified
quickstart/app.py (+12/-4)
quickstart/manage.py (+15/-13)
quickstart/tests/helpers.py (+17/-0)
quickstart/tests/test_app.py (+38/-11)
quickstart/tests/test_manage.py (+32/-7)
quickstart/tests/test_utils.py (+73/-0)
quickstart/utils.py (+33/-0)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/open-urls
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+193297@code.launchpad.net

Description of the change

Deploy a bundle from a HTTP(S) url.

The bundle argument can be a file path or
a http/https URL pointing to a bundle YAML.

Also get the URL of the last Juju GUI charm
revision from charmworld (API 2 for now).

Tests: `make check`.

QA:
I copied a bundle YAML over here:
http://dpaste.com/1435065/plain/
You should be able to deploy that bundle
by running the following (after juju switching to ec2 or similar):
`.venv/bin/python juju-quickstart http://dpaste.com/1435065/plain/`
Remember to destroy your ec2 environment...

https://codereview.appspot.com/19870043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+193297_code.launchpad.net,

Message:
Please take a look.

Description:
Deploy a bundle from a HTTP(S) url.

The bundle argument can be a file path or
a http/https URL pointing to a bundle YAML.

Also get the URL of the last Juju GUI charm
revision from charmworld (API 2 for now).

Tests: `make check`.

QA:
I copied a bundle YAML over here:
http://dpaste.com/1435065/plain/
You should be able to deploy that bundle
by running the following (after juju switching to ec2 or similar):
`.venv/bin/python juju-quickstart http://dpaste.com/1435065/plain/`
Remember to destroy your ec2 environment...

https://code.launchpad.net/~frankban/juju-quickstart/open-urls/+merge/193297

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/19870043/

Affected files (+224, -35 lines):
   A [revision details]
   M quickstart/app.py
   M quickstart/manage.py
   M quickstart/tests/helpers.py
   M quickstart/tests/test_app.py
   M quickstart/tests/test_manage.py
   M quickstart/tests/test_utils.py
   M quickstart/utils.py

Revision history for this message
Gary Poster (gary) wrote :

Code LGTM with trivial update. Trying QA now.

https://codereview.appspot.com/19870043/diff/1/quickstart/app.py
File quickstart/app.py (right):

https://codereview.appspot.com/19870043/diff/1/quickstart/app.py#newcode126
quickstart/app.py:126: logging.warn(msg.format(err))
nice.

https://codereview.appspot.com/19870043/diff/1/quickstart/tests/test_app.py
File quickstart/tests/test_app.py (right):

https://codereview.appspot.com/19870043/diff/1/quickstart/tests/test_app.py#newcode263
quickstart/tests/test_app.py:263: with
self.patch_get_charm_url(side_effect=IOError('boo!')):
You scared me.

https://codereview.appspot.com/19870043/diff/1/quickstart/utils.py
File quickstart/utils.py (right):

https://codereview.appspot.com/19870043/diff/1/quickstart/utils.py#newcode35
quickstart/utils.py:35: # Switch to charmworld API 3 when the 500 error
is fixed.
We had a fix and a deployment, and
http://manage.jujucharms.com/api/3/charm/precise/juju-gui works now
(yay!)

https://codereview.appspot.com/19870043/

Revision history for this message
Gary Poster (gary) wrote :

Go you and your bad 100% test coverage self.

qa good (though EC2 left a bunch of services in pending state and the
haproxy and daisy charms were DOA :-/ )

Thank you!

https://codereview.appspot.com/19870043/

11. By Francesco Banconi

Changes as per review.

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Deploy a bundle from a HTTP(S) url.

The bundle argument can be a file path or
a http/https URL pointing to a bundle YAML.

Also get the URL of the last Juju GUI charm
revision from charmworld (API 2 for now).

Tests: `make check`.

QA:
I copied a bundle YAML over here:
http://dpaste.com/1435065/plain/
You should be able to deploy that bundle
by running the following (after juju switching to ec2 or similar):
`.venv/bin/python juju-quickstart http://dpaste.com/1435065/plain/`
Remember to destroy your ec2 environment...

R=gary.poster
CC=
https://codereview.appspot.com/19870043

https://codereview.appspot.com/19870043/diff/1/quickstart/utils.py
File quickstart/utils.py (right):

https://codereview.appspot.com/19870043/diff/1/quickstart/utils.py#newcode35
quickstart/utils.py:35: # Switch to charmworld API 3 when the 500 error
is fixed.
On 2013/10/30 20:58:55, gary.poster wrote:
> We had a fix and a deployment, and
> http://manage.jujucharms.com/api/3/charm/precise/juju-gui works now
(yay!)

Cool! Done.

https://codereview.appspot.com/19870043/

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks for the review Gary!

https://codereview.appspot.com/19870043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'quickstart/app.py'
--- quickstart/app.py 2013-10-30 10:16:36 +0000
+++ quickstart/app.py 2013-10-31 08:49:45 +0000
@@ -19,6 +19,7 @@
19from __future__ import print_function19from __future__ import print_function
20import functools20import functools
21import json21import json
22import logging
2223
23import jujuclient24import jujuclient
2425
@@ -28,6 +29,11 @@
28)29)
2930
3031
32# The default Juju GUI charm URL to use when it is not possible to retrieve it
33# from the charmworld API, e.g. due to temporary connection/charmworld errors.
34DEFAULT_CHARM_URL = 'cs:precise/juju-gui-78'
35
36
31class ProgramExit(Exception):37class ProgramExit(Exception):
32 """An error occurred while setting up the Juju environment.38 """An error occurred while setting up the Juju environment.
3339
@@ -113,10 +119,12 @@
113119
114 Raise a ProgramExit if the API server returns an error response.120 Raise a ProgramExit if the API server returns an error response.
115 """121 """
116 # XXX 2013-10-17 frankban:122 try:
117 # Retrieve the URL of the last charm revision from123 charm_url = utils.get_charm_url()
118 # manage.jujucharms.com.124 except (IOError, ValueError) as err:
119 charm_url = 'cs:precise/juju-gui-78'125 msg = 'unable to retrieve the Juju GUI charm URL from the API: {}'
126 logging.warn(msg.format(err))
127 charm_url = DEFAULT_CHARM_URL
120 try:128 try:
121 env.deploy(service_name, charm_url, to=0)129 env.deploy(service_name, charm_url, to=0)
122 env.expose(service_name)130 env.expose(service_name)
123131
=== modified file 'quickstart/manage.py'
--- quickstart/manage.py 2013-10-30 10:16:36 +0000
+++ quickstart/manage.py 2013-10-31 08:49:45 +0000
@@ -46,21 +46,26 @@
46 """Validate and process the bundle options.46 """Validate and process the bundle options.
4747
48 Populate the options namespace with the following names:48 Populate the options namespace with the following names:
49 - bundle_file: the full path to the bundle file;
50 - bundle_name: the name of the bundle;49 - bundle_name: the name of the bundle;
51 - bundle_services: a list of service names included in the bundle;50 - bundle_services: a list of service names included in the bundle;
52 - bundle_yaml: the YAML encoded contents of the bundle.51 - bundle_yaml: the YAML encoded contents of the bundle.
5352
54 Exit with an error if the bundle options are not valid.53 Exit with an error if the bundle options are not valid.
55 """54 """
56 # XXX 2013-10-18 frankban:55 bundle = options.bundle
57 # This function is supposed to also support bundle URLs.56 if bundle.startswith('http://') or bundle.startswith('https://'):
58 bundle_file = os.path.abspath(os.path.expanduser(options.bundle))57 # Load the bundle URL.
59 # Load the bundle file.58 try:
60 try:59 bundle_yaml = utils.urlread(bundle)
61 bundle_yaml = open(bundle_file).read()60 except IOError as err:
62 except IOError as err:61 return parser.error('unable to open bundle URL: {}'.format(err))
63 return parser.error('unable to open bundle file: {}'.format(err))62 else:
63 # Load the bundle file.
64 bundle_file = os.path.abspath(os.path.expanduser(bundle))
65 try:
66 bundle_yaml = open(bundle_file).read()
67 except IOError as err:
68 return parser.error('unable to open bundle file: {}'.format(err))
64 # Validate the bundle.69 # Validate the bundle.
65 try:70 try:
66 bundle_name, bundle_services = utils.parse_bundle(71 bundle_name, bundle_services = utils.parse_bundle(
@@ -68,7 +73,6 @@
68 except ValueError as err:73 except ValueError as err:
69 return parser.error(str(err))74 return parser.error(str(err))
70 # Update the options namespace with the new values.75 # Update the options namespace with the new values.
71 options.bundle_file = bundle_file
72 options.bundle_name = bundle_name76 options.bundle_name = bundle_name
73 options.bundle_services = bundle_services77 options.bundle_services = bundle_services
74 options.bundle_yaml = bundle_yaml78 options.bundle_yaml = bundle_yaml
@@ -140,11 +144,9 @@
140 env_help = '{} (%(default)s)'.format(env_help)144 env_help = '{} (%(default)s)'.format(env_help)
141 # Create and set up the arguments parser.145 # Create and set up the arguments parser.
142 parser = argparse.ArgumentParser(description=quickstart.__doc__)146 parser = argparse.ArgumentParser(description=quickstart.__doc__)
143 # XXX 2013-10-18 frankban:
144 # Make it possible to pass a URL as bundle argument.
145 parser.add_argument(147 parser.add_argument(
146 'bundle', default=None, nargs='?',148 'bundle', default=None, nargs='?',
147 help='The path to the bundle file to deploy')149 help='The bundle URL or the path to the bundle file to deploy')
148 parser.add_argument(150 parser.add_argument(
149 '-e', '--environment', default=default_env_name, dest='env_name',151 '-e', '--environment', default=default_env_name, dest='env_name',
150 help=env_help)152 help=env_help)
151153
=== modified file 'quickstart/tests/helpers.py'
--- quickstart/tests/helpers.py 2013-10-30 10:16:36 +0000
+++ quickstart/tests/helpers.py 2013-10-31 08:49:45 +0000
@@ -100,6 +100,23 @@
100 return env_file.name100 return env_file.name
101101
102102
103class UrlReadTestsMixin(object):
104 """Expose a method to mock the quickstart.utils.urlread helper function."""
105
106 def patch_urlread(self, contents=None, error=False):
107 """Patch the quickstart.utils.urlread helper function.
108
109 If contents is not None, urlread() will return the provided contents.
110 If error is set to True, an IOError will be simulated.
111 """
112 mock_urlread = mock.Mock()
113 if contents is not None:
114 mock_urlread.return_value = contents
115 if error:
116 mock_urlread.side_effect = IOError('bad wolf')
117 return mock.patch('quickstart.utils.urlread', mock_urlread)
118
119
103class ValueErrorTestsMixin(object):120class ValueErrorTestsMixin(object):
104 """Set up some base methods for testing functions raising ValueErrors."""121 """Set up some base methods for testing functions raising ValueErrors."""
105122
106123
=== modified file 'quickstart/tests/test_app.py'
--- quickstart/tests/test_app.py 2013-10-30 10:16:36 +0000
+++ quickstart/tests/test_app.py 2013-10-31 08:49:45 +0000
@@ -235,12 +235,37 @@
235235
236class TestDeployGui(ProgramExitTestsMixin, unittest.TestCase):236class TestDeployGui(ProgramExitTestsMixin, unittest.TestCase):
237237
238 charm_url = 'cs:precise/juju-gui-42'
239
240 def patch_get_charm_url(self, side_effect=None):
241 """Patch the get_charm_url helper function."""
242 if side_effect is None:
243 side_effect = [self.charm_url]
244 mock_get_charm_url = mock.Mock(side_effect=side_effect)
245 return mock.patch('quickstart.utils.get_charm_url', mock_get_charm_url)
246
238 def test_deployment(self):247 def test_deployment(self):
239 # The function correctly deploys and exposes the service.248 # The function correctly deploys and exposes the service, retrieving
240 env = mock.Mock()249 # the charm URL from the charmworld API.
241 app.deploy_gui(env, 'my-gui')250 env = mock.Mock()
242 env.assert_has_calls([251 with self.patch_get_charm_url():
243 mock.call.deploy('my-gui', 'cs:precise/juju-gui-78', to=0),252 app.deploy_gui(env, 'my-gui')
253 env.assert_has_calls([
254 mock.call.deploy('my-gui', 'cs:precise/juju-gui-42', to=0),
255 mock.call.expose('my-gui')
256 ])
257
258 def test_deployment_default_charm_url(self):
259 # The function correctly deploys and exposes the service, even if it is
260 # not able to retrieve the charm URL from the charmworld API.
261 env = mock.Mock()
262 log = 'unable to retrieve the Juju GUI charm URL from the API: boo!'
263 with self.patch_get_charm_url(side_effect=IOError('boo!')):
264 # A warning is logged which notifies we are using the default URL.
265 with helpers.assert_logs([log], level='warn'):
266 app.deploy_gui(env, 'my-gui')
267 env.assert_has_calls([
268 mock.call.deploy('my-gui', app.DEFAULT_CHARM_URL, to=0),
244 mock.call.expose('my-gui')269 mock.call.expose('my-gui')
245 ])270 ])
246271
@@ -249,20 +274,22 @@
249 env = mock.Mock()274 env = mock.Mock()
250 env.deploy.side_effect = self.make_env_error('service already exists')275 env.deploy.side_effect = self.make_env_error('service already exists')
251 expected = 'bad API server response: service already exists'276 expected = 'bad API server response: service already exists'
252 with self.assert_program_exit(expected):277 with self.patch_get_charm_url():
253 app.deploy_gui(env, 'another-gui')278 with self.assert_program_exit(expected):
279 app.deploy_gui(env, 'another-gui')
254 env.deploy.assert_called_once_with(280 env.deploy.assert_called_once_with(
255 'another-gui', 'cs:precise/juju-gui-78', to=0)281 'another-gui', 'cs:precise/juju-gui-42', to=0)
256282
257 def test_other_errors(self):283 def test_other_errors(self):
258 # Any other errors occurred during the process are not trapped.284 # Any other errors occurred during the process are not trapped.
259 error = ValueError('explode!')285 error = ValueError('explode!')
260 env = mock.Mock()286 env = mock.Mock()
261 env.expose.side_effect = error287 env.expose.side_effect = error
262 with self.assertRaises(ValueError) as context_manager:288 with self.patch_get_charm_url():
263 app.deploy_gui(env, 'juju-gui')289 with self.assertRaises(ValueError) as context_manager:
290 app.deploy_gui(env, 'juju-gui')
264 env.deploy.assert_called_once_with(291 env.deploy.assert_called_once_with(
265 'juju-gui', 'cs:precise/juju-gui-78', to=0)292 'juju-gui', 'cs:precise/juju-gui-42', to=0)
266 env.expose.assert_called_once_with('juju-gui')293 env.expose.assert_called_once_with('juju-gui')
267 self.assertIs(error, context_manager.exception)294 self.assertIs(error, context_manager.exception)
268295
269296
=== modified file 'quickstart/tests/test_manage.py'
--- quickstart/tests/test_manage.py 2013-10-30 10:16:36 +0000
+++ quickstart/tests/test_manage.py 2013-10-31 08:49:45 +0000
@@ -46,7 +46,9 @@
46 mock_exit.assert_called_once_with(0)46 mock_exit.assert_called_once_with(0)
4747
4848
49class TestValidateBundle(helpers.BundleFileTestsMixin, unittest.TestCase):49class TestValidateBundle(
50 helpers.BundleFileTestsMixin, helpers.UrlReadTestsMixin,
51 unittest.TestCase):
5052
51 def setUp(self):53 def setUp(self):
52 self.parser = mock.Mock()54 self.parser = mock.Mock()
@@ -55,12 +57,24 @@
55 """Return a mock options object which includes the passed arguments."""57 """Return a mock options object which includes the passed arguments."""
56 return mock.Mock(bundle=bundle, bundle_name=bundle_name)58 return mock.Mock(bundle=bundle, bundle_name=bundle_name)
5759
58 def test_resulting_options(self):60 def test_resulting_options_from_file(self):
59 # The options object is correctly set up.61 # The options object is correctly set up when a bundle file is passed.
60 bundle_file = self.make_bundle_file()62 bundle_file = self.make_bundle_file()
61 options = self.make_options(bundle_file, bundle_name='bundle1')63 options = self.make_options(bundle_file, bundle_name='bundle1')
62 manage._validate_bundle(options, self.parser)64 manage._validate_bundle(options, self.parser)
63 self.assertEqual(bundle_file, options.bundle_file)65 self.assertEqual('bundle1', options.bundle_name)
66 self.assertEqual(
67 ['mysql', 'wordpress'], sorted(options.bundle_services))
68 self.assertEqual(open(bundle_file).read(), options.bundle_yaml)
69
70 def test_resulting_options_from_url(self):
71 # The options object is correctly set up when a bundle URL is passed.
72 bundle_file = self.make_bundle_file()
73 url = 'http://example.com/bundle.yaml'
74 options = self.make_options(url, bundle_name='bundle1')
75 with self.patch_urlread(contents=self.valid_bundle) as mock_urlread:
76 manage._validate_bundle(options, self.parser)
77 mock_urlread.assert_called_once_with(url)
64 self.assertEqual('bundle1', options.bundle_name)78 self.assertEqual('bundle1', options.bundle_name)
65 self.assertEqual(79 self.assertEqual(
66 ['mysql', 'wordpress'], sorted(options.bundle_services))80 ['mysql', 'wordpress'], sorted(options.bundle_services))
@@ -78,7 +92,7 @@
78 options = self.make_options(bundle=path, bundle_name='bundle2')92 options = self.make_options(bundle=path, bundle_name='bundle2')
79 with mock.patch('os.environ', {'HOME': base_path}):93 with mock.patch('os.environ', {'HOME': base_path}):
80 manage._validate_bundle(options, self.parser)94 manage._validate_bundle(options, self.parser)
81 self.assertEqual(bundle_file, options.bundle_file)95 self.assertEqual(self.valid_bundle, options.bundle_yaml)
8296
83 def test_bundle_file_not_found(self):97 def test_bundle_file_not_found(self):
84 # A parser error is invoked if the bundle file is not found.98 # A parser error is invoked if the bundle file is not found.
@@ -90,8 +104,19 @@
90 )104 )
91 self.parser.error.assert_called_once_with(expected)105 self.parser.error.assert_called_once_with(expected)
92106
93 def test_error_parsing_bundle_file(self):107 def test_url_error(self):
94 # A parser error is invoked if an error occurs parsing the bundle file.108 # A parser error is invoked if the bundle cannot be fetched from the
109 # provided URL.
110 url = 'http://example.com/bundle.yaml'
111 options = self.make_options(url)
112 with self.patch_urlread(error=True) as mock_urlread:
113 manage._validate_bundle(options, self.parser)
114 mock_urlread.assert_called_once_with(url)
115 self.parser.error.assert_called_once_with(
116 'unable to open bundle URL: bad wolf')
117
118 def test_error_parsing_bundle_contents(self):
119 # A parser error is invoked if an error occurs parsing the bundle YAML.
95 bundle_file = self.make_bundle_file()120 bundle_file = self.make_bundle_file()
96 options = self.make_options(bundle_file, bundle_name='no-such')121 options = self.make_options(bundle_file, bundle_name='no-such')
97 manage._validate_bundle(options, self.parser)122 manage._validate_bundle(options, self.parser)
98123
=== modified file 'quickstart/tests/test_utils.py'
--- quickstart/tests/test_utils.py 2013-10-30 10:16:36 +0000
+++ quickstart/tests/test_utils.py 2013-10-31 08:49:45 +0000
@@ -16,7 +16,11 @@
1616
17"""Tests for the Juju Quickstart utility functions and classes."""17"""Tests for the Juju Quickstart utility functions and classes."""
1818
19import httplib
20import json
21import socket
19import unittest22import unittest
23import urllib2
2024
21import mock25import mock
22import yaml26import yaml
@@ -71,6 +75,35 @@
71 utils.call('echo', 'we are the borg!')75 utils.call('echo', 'we are the borg!')
7276
7377
78class TestGetCharmUrl(helpers.UrlReadTestsMixin, unittest.TestCase):
79
80 def test_charm_url(self):
81 # The Juju GUI charm URL is correctly returned.
82 contents = json.dumps({'charm': {'url': 'cs:precise/juju-gui-42'}})
83 with self.patch_urlread(contents=contents) as mock_urlread:
84 charm_url = utils.get_charm_url()
85 self.assertEqual('cs:precise/juju-gui-42', charm_url)
86 mock_urlread.assert_called_once_with(utils.CHARMWORLD_API)
87
88 def test_io_error(self):
89 # IOErrors are properly propagated.
90 with self.patch_urlread(error=True) as mock_urlread:
91 with self.assertRaises(IOError) as context_manager:
92 utils.get_charm_url()
93 mock_urlread.assert_called_once_with(utils.CHARMWORLD_API)
94 self.assertEqual('bad wolf', str(context_manager.exception))
95
96 def test_value_error(self):
97 # A ValueError is raised if the API response is not valid.
98 contents = json.dumps({'charm': {}})
99 with self.patch_urlread(contents=contents) as mock_urlread:
100 with self.assertRaises(ValueError) as context_manager:
101 utils.get_charm_url()
102 mock_urlread.assert_called_once_with(utils.CHARMWORLD_API)
103 self.assertEqual(
104 'unable to find the charm URL', str(context_manager.exception))
105
106
74class TestGetDefaultEnvName(helpers.CallTestsMixin, unittest.TestCase):107class TestGetDefaultEnvName(helpers.CallTestsMixin, unittest.TestCase):
75108
76 def test_environment_variable(self):109 def test_environment_variable(self):
@@ -354,6 +387,46 @@
354 utils.unit_changes(self.unit, changeset))387 utils.unit_changes(self.unit, changeset))
355388
356389
390class TestUrlread(unittest.TestCase):
391
392 def patch_urlopen(self, contents=None, error=None):
393 """Patch the urllib2.urlopen function.
394
395 If contents is not None, the read() method of the returned mock object
396 returns the given contents.
397 If an error is provided, the call raises the error.
398 """
399 mock_urlopen = mock.Mock()
400 if contents is not None:
401 mock_urlopen().read.return_value = contents
402 if error is not None:
403 mock_urlopen.side_effect = error
404 mock_urlopen.reset_mock()
405 return mock.patch('urllib2.urlopen', mock_urlopen)
406
407 def test_contents(self):
408 # The URL contents are correctly returned.
409 with self.patch_urlopen(contents='URL contents') as mock_urlopen:
410 contents = utils.urlread('http://example.com/path/')
411 self.assertEqual('URL contents', contents)
412 mock_urlopen.assert_called_once_with('http://example.com/path/')
413
414 def test_errors(self):
415 # An IOError is raised if an error occurs connecting to the API.
416 errors = {
417 'httplib HTTPException': httplib.HTTPException,
418 'socket error': socket.error,
419 'urllib2 URLError': urllib2.URLError,
420 }
421 for message, exception_class in errors.items():
422 exception = exception_class(message)
423 with self.patch_urlopen(error=exception) as mock_urlopen:
424 with self.assertRaises(IOError) as context_manager:
425 utils.urlread('http://example.com/path/')
426 mock_urlopen.assert_called_once_with('http://example.com/path/')
427 self.assertEqual(message, str(context_manager.exception))
428
429
357class TestUtf8(unittest.TestCase):430class TestUtf8(unittest.TestCase):
358431
359 def test_unicode(self):432 def test_unicode(self):
360433
=== modified file 'quickstart/utils.py'
--- quickstart/utils.py 2013-10-30 10:16:36 +0000
+++ quickstart/utils.py 2013-10-31 08:49:45 +0000
@@ -17,16 +17,22 @@
17"""Juju Quickstart utility functions and classes."""17"""Juju Quickstart utility functions and classes."""
1818
19import collections19import collections
20import httplib
21import json
20import logging22import logging
21import os23import os
22import pipes24import pipes
23import re25import re
26import socket
24import subprocess27import subprocess
28import urllib2
2529
26import yaml30import yaml
2731
28# Compile the regular expression used to parse the "juju switch" output.32# Compile the regular expression used to parse the "juju switch" output.
29_juju_switch_expression = re.compile(r'Current environment: "([\w-]+)"\n')33_juju_switch_expression = re.compile(r'Current environment: "([\w-]+)"\n')
34# Define the URL containing information about the last Juju GUI charm version.
35CHARMWORLD_API = 'http://manage.jujucharms.com/api/3/charm/precise/juju-gui'
3036
3137
32def call(*args):38def call(*args):
@@ -52,6 +58,19 @@
52 return retcode, output, error58 return retcode, output, error
5359
5460
61def get_charm_url():
62 """Return the charm URL of the latest Juju GUI charm revision.
63
64 Raise an IOError if any problems occur connecting to the API endpoint.
65 Raise a ValueError if the API returns invalid data.
66 """
67 charm_info = json.loads(urlread(CHARMWORLD_API))
68 charm_url = charm_info.get('charm', {}).get('url')
69 if charm_url is None:
70 raise ValueError('unable to find the charm URL')
71 return charm_url
72
73
55def get_default_env_name():74def get_default_env_name():
56 """Return the current Juju environment name.75 """Return the current Juju environment name.
5776
@@ -212,6 +231,20 @@
212 return change231 return change
213232
214233
234def urlread(url):
235 """Open the given URL and return the page contents.
236
237 Raise an IOError if any problems occur.
238 """
239 try:
240 response = urllib2.urlopen(url)
241 except urllib2.URLError as err:
242 raise IOError(err.reason)
243 except (httplib.HTTPException, socket.error, urllib2.HTTPError) as err:
244 raise IOError(str(err))
245 return response.read()
246
247
215def utf8(value):248def utf8(value):
216 """Return the utf8 encoded version of the given value.249 """Return the utf8 encoded version of the given value.
217250

Subscribers

People subscribed via source and target branches