Merge lp:~frankban/juju-quickstart/support-json into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 10
Proposed branch: lp:~frankban/juju-quickstart/support-json
Merge into: lp:juju-quickstart
Diff against target: 564 lines (+191/-50)
12 files modified
.bzrignore (+1/-0)
HACKING.rst (+1/-1)
README.rst (+1/-1)
quickstart/__init__.py (+1/-1)
quickstart/app.py (+20/-10)
quickstart/manage.py (+36/-10)
quickstart/tests/helpers.py (+26/-5)
quickstart/tests/test_app.py (+43/-12)
quickstart/tests/test_manage.py (+38/-2)
quickstart/tests/test_utils.py (+19/-6)
quickstart/utils.py (+4/-1)
setup.py (+1/-1)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/support-json
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+194172@code.launchpad.net

Description of the change

JSON support + minor fixes.

This branch includes official JSON
support for bundle deployments.

This branch also contains a number of
minor fixes/improvements:

- Added the possibility to provide a
  customized Juju GUI charm URL.

- Added support for providing a bundle
  directory (a dir containing a
  bundles.yaml file): fix bug 1247181.

- Some documentation clean up.

- Added an option to decline opening the
  browser.

- The Juju env is bootstrapped passing
  --debug when quickstart is run in debug
  mode.

- Bumped version up.

Tests: `make check`.

QA:
I uploaded a bundle JSON here:
http://pastebin.com/raw.php?i=4mDCcagp
So it is possible to check the new features
by running the following (expect a scary output):
.venv/bin/python juju-quickstart --debug --no-browser \
 --gui-charm-url cs:~juju-gui/precise/juju-gui-116 \
 -e ec2 http://pastebin.com/raw.php?i=4mDCcagp
At the end of the process, locate the GUI url
printed before the last debug messages, and check
the GUI is up, running and the bundle is deploying.
Remember to destroy your ec2 env.
Thank you!

https://codereview.appspot.com/22300044/

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

Reviewers: mp+194172_code.launchpad.net,

Message:
Please take a look.

Description:
JSON support + minor fixes.

This branch includes official JSON
support for bundle deployments.

This branch also contains a number of
minor fixes/improvements:

- Added the possibility to provide a
   customized Juju GUI charm URL.

- Added support for providing a bundle
   directory (a dir containing a
   bundles.yaml file): fix bug 1247181.

- Some documentation clean up.

- Added an option to decline opening the
   browser.

- The Juju env is bootstrapped passing
   --debug when quickstart is run in debug
   mode.

- Bumped version up.

Tests: `make check`.

QA:
I uploaded a bundle JSON here:
http://pastebin.com/raw.php?i=4mDCcagp
So it is possible to check the new features
by running the following (expect a scary output):
.venv/bin/python juju-quickstart --debug --no-browser \
 --gui-charm-url cs:~juju-gui/precise/juju-gui-116 \
 -e ec2 http://pastebin.com/raw.php?i=4mDCcagp
At the end of the process, locate the GUI url
printed before the last debug messages, and check
the GUI is up, running and the bundle is deploying.
Remember to destroy your ec2 env.
Thank you!

https://code.launchpad.net/~frankban/juju-quickstart/support-json/+merge/194172

(do not edit description out of merge proposal)

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

Affected files (+190, -50 lines):
   M .bzrignore
   M HACKING.rst
   M README.rst
   A [revision details]
   M quickstart/__init__.py
   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
   M setup.py

Revision history for this message
Madison Scott-Clary (makyo) wrote :
Revision history for this message
Gary Poster (gary) wrote :

LGTM with trivial. Thank you!

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

https://codereview.appspot.com/22300044/diff/1/quickstart/app.py#newcode123
quickstart/app.py:123: and the optional Juju GUI charm URL. If the charm
URL is not provided, the
Maybe give an example of the expected format, e.g.
cs:~juju-gui/precise/juju-gui-116 ? Maybe that's more
valuable/important for the commandline help.

https://codereview.appspot.com/22300044/diff/1/quickstart/manage.py
File quickstart/manage.py (right):

https://codereview.appspot.com/22300044/diff/1/quickstart/manage.py#newcode181
quickstart/manage.py:181: 'provided, the last release of the GUI will be
deployed')
This would be the ideal place to include an example IMO, like mentioning
that you can get the dev charm by using cs:~juju-gui/precise/juju-gui

https://codereview.appspot.com/22300044/

16. By Francesco Banconi

Changes as per review.

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

*** Submitted:

JSON support + minor fixes.

This branch includes official JSON
support for bundle deployments.

This branch also contains a number of
minor fixes/improvements:

- Added the possibility to provide a
   customized Juju GUI charm URL.

- Added support for providing a bundle
   directory (a dir containing a
   bundles.yaml file): fix bug 1247181.

- Some documentation clean up.

- Added an option to decline opening the
   browser.

- The Juju env is bootstrapped passing
   --debug when quickstart is run in debug
   mode.

- Bumped version up.

Tests: `make check`.

QA:
I uploaded a bundle JSON here:
http://pastebin.com/raw.php?i=4mDCcagp
So it is possible to check the new features
by running the following (expect a scary output):
.venv/bin/python juju-quickstart --debug --no-browser \
 --gui-charm-url cs:~juju-gui/precise/juju-gui-116 \
 -e ec2 http://pastebin.com/raw.php?i=4mDCcagp
At the end of the process, locate the GUI url
printed before the last debug messages, and check
the GUI is up, running and the bundle is deploying.
Remember to destroy your ec2 env.
Thank you!

R=
CC=
https://codereview.appspot.com/22300044

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

https://codereview.appspot.com/22300044/diff/1/quickstart/app.py#newcode123
quickstart/app.py:123: and the optional Juju GUI charm URL. If the charm
URL is not provided, the
On 2013/11/06 17:40:43, gary.poster wrote:
> Maybe give an example of the expected format, e.g.
> cs:~juju-gui/precise/juju-gui-116 ? Maybe that's more
valuable/important for
> the commandline help.

Done.

https://codereview.appspot.com/22300044/diff/1/quickstart/manage.py
File quickstart/manage.py (right):

https://codereview.appspot.com/22300044/diff/1/quickstart/manage.py#newcode181
quickstart/manage.py:181: 'provided, the last release of the GUI will be
deployed')
On 2013/11/06 17:40:43, gary.poster wrote:
> This would be the ideal place to include an example IMO, like
mentioning that
> you can get the dev charm by using cs:~juju-gui/precise/juju-gui

Done.

https://codereview.appspot.com/22300044/

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2013-10-14 15:54:00 +0000
3+++ .bzrignore 2013-11-06 17:54:24 +0000
4@@ -1,4 +1,5 @@
5 .coverage
6+.DS_Store
7 .emacs*
8 .venv
9 build
10
11=== modified file 'HACKING.rst'
12--- HACKING.rst 2013-10-14 15:24:29 +0000
13+++ HACKING.rst 2013-11-06 17:54:24 +0000
14@@ -5,7 +5,7 @@
15 environment in very few steps. The environment is bootstrapped and set up so
16 that it can be managed using a Web interface (the Juju GUI).
17
18-Bundle support is a planned feature, and will allow for setting up a complete
19+Bundle deployments are also supported, and allow for setting up a complete
20 topology of services in one simple command.
21
22 Creating a development environment
23
24=== modified file 'README.rst'
25--- README.rst 2013-10-14 15:24:29 +0000
26+++ README.rst 2013-11-06 17:54:24 +0000
27@@ -5,5 +5,5 @@
28 environment in very few steps. The environment is bootstrapped and set up so
29 that it can be managed using a Web interface (the Juju GUI).
30
31-Bundle support is a planned feature, and will allow for setting up a complete
32+Bundle deployments are also supported, and allow for setting up a complete
33 topology of services in one simple command.
34
35=== modified file 'quickstart/__init__.py'
36--- quickstart/__init__.py 2013-10-30 10:16:36 +0000
37+++ quickstart/__init__.py 2013-11-06 17:54:24 +0000
38@@ -19,7 +19,7 @@
39 that it can be managed using a Web interface (the Juju GUI).
40 """
41
42-VERSION = (0, 2, 0)
43+VERSION = (0, 3, 0)
44
45
46 def get_version():
47
48=== modified file 'quickstart/app.py'
49--- quickstart/app.py 2013-10-30 17:37:46 +0000
50+++ quickstart/app.py 2013-11-06 17:54:24 +0000
51@@ -48,13 +48,18 @@
52 return 'juju-quickstart: error: {}'.format(self.message)
53
54
55-def bootstrap(env_name):
56+def bootstrap(env_name, debug=False):
57 """Bootstrap the Juju environment with the given name.
58
59+ If debug is True, bootstrap the environment passing the --debug flag.
60+
61 Return when the bootstrap node is ready.
62 Raise a ProgramExit if any error occurs in the bootstrap process.
63 """
64- retcode, _, error = utils.call('juju', 'bootstrap', '-e', env_name)
65+ cmd = ['juju', 'bootstrap', '-e', env_name]
66+ if debug:
67+ cmd.append('--debug')
68+ retcode, _, error = utils.call(*cmd)
69 if retcode:
70 raise ProgramExit(error)
71 # Call "juju status" multiple times until the bootstrap node is ready.
72@@ -111,20 +116,25 @@
73 return env
74
75
76-def deploy_gui(env, service_name):
77+def deploy_gui(env, service_name, charm_url=None):
78 """Deploy and expose the given service, reusing the bootstrap node.
79
80 Receive an authenticated Juju Environment instance, the name of the service
81- and the corresponding charm URL.
82+ and the optional Juju GUI charm URL, e.g. cs:~juju-gui/precise/juju-gui-42.
83+ If the charm URL is not provided, the function tries to retrieve it from
84+ charmworld. In this case a default charm URL is used if charmworld is not
85+ available.
86
87 Raise a ProgramExit if the API server returns an error response.
88 """
89- try:
90- charm_url = utils.get_charm_url()
91- except (IOError, ValueError) as err:
92- msg = 'unable to retrieve the Juju GUI charm URL from the API: {}'
93- logging.warn(msg.format(err))
94- charm_url = DEFAULT_CHARM_URL
95+ if charm_url is None:
96+ try:
97+ charm_url = utils.get_charm_url()
98+ except (IOError, ValueError) as err:
99+ msg = 'unable to retrieve the Juju GUI charm URL from the API: {}'
100+ logging.warn(msg.format(err))
101+ charm_url = DEFAULT_CHARM_URL
102+ print('charm URL: {}'.format(charm_url))
103 try:
104 env.deploy(service_name, charm_url, to=0)
105 env.expose(service_name)
106
107=== modified file 'quickstart/manage.py'
108--- quickstart/manage.py 2013-10-30 17:37:46 +0000
109+++ quickstart/manage.py 2013-11-06 17:54:24 +0000
110@@ -62,6 +62,8 @@
111 else:
112 # Load the bundle file.
113 bundle_file = os.path.abspath(os.path.expanduser(bundle))
114+ if os.path.isdir(bundle_file):
115+ bundle_file = os.path.join(bundle_file, 'bundles.yaml')
116 try:
117 bundle_yaml = open(bundle_file).read()
118 except IOError as err:
119@@ -131,9 +133,19 @@
120
121 Return the options as a namespace containing the following attributes:
122 - admin_secret: the password to use to access the Juju API;
123+ - bundle: the optional bundle (path or URL) to be deployed;
124+ - charm_url: the Juju GUI charm URL or None if not specified;
125+ - debug: whether debug mode is activated;
126 - env_file: the absolute path of the Juju environments.yaml file;
127 - env_name: the name of the Juju environment to use;
128- - env_type: the provider type of the selected Juju environment.
129+ - env_type: the provider type of the selected Juju environment;
130+ - open_browser: whether the GUI browser must be opened.
131+
132+ The following attributes will also be included in the namespace if a bundle
133+ deployment is requested:
134+ - bundle_name: the name of the bundle to be deployed;
135+ - bundle_services: a list of service names included in the bundle;
136+ - bundle_yaml: the YAML encoded contents of the bundle.
137
138 Exit with an error if the provided arguments are not valid.
139 """
140@@ -146,24 +158,39 @@
141 parser = argparse.ArgumentParser(description=quickstart.__doc__)
142 parser.add_argument(
143 'bundle', default=None, nargs='?',
144- help='The bundle URL or the path to the bundle file to deploy')
145+ help='The optional bundle to be deployed. The bundle can be '
146+ '1) a path to a YAML/JSON file or '
147+ '2) a path to a directory containing a bundles.yaml file or '
148+ '3) a URL (starting with http:// or https://) to a YAML/JSON')
149 parser.add_argument(
150 '-e', '--environment', default=default_env_name, dest='env_name',
151 help=env_help)
152 parser.add_argument(
153 '-n', '--bundle-name', default=None, dest='bundle_name',
154 help='The name of the bundle to use. This must be included in the '
155- 'provided bundle file. Specifying the bundle name is not '
156- 'required if the bundle file only contains one bundle. This '
157+ 'provided bundle YAML/JSON. Specifying the bundle name is not '
158+ 'required if the bundle YAML/JSON only contains one bundle. This '
159 'option is ignored if the bundle file is not specified')
160 parser.add_argument(
161 '--environments-file',
162 default=os.path.join(juju_home, 'environments.yaml'), dest='env_file',
163 help='The path to the Juju environments YAML file (%(default)s)')
164 parser.add_argument(
165+ '--gui-charm-url', dest='charm_url',
166+ help='The Juju GUI charm URL to deploy in the environment. If not '
167+ 'provided, the last release of the GUI will be deployed. The '
168+ 'charm URL must include the charm version, e.g. '
169+ 'cs:~juju-gui/precise/juju-gui-116')
170+ parser.add_argument(
171+ '--no-browser', action='store_false', dest='open_browser',
172+ help='Avoid opening the browser to the GUI at the end of the process')
173+ parser.add_argument(
174 '--version', action='version', version='%(prog)s {}'.format(version))
175 parser.add_argument(
176- '--debug', action='store_true', help='Turn debug mode on')
177+ '--debug', action='store_true',
178+ help='Turn debug mode on. When enabled, all the subcommands and API '
179+ 'calls are logged to stdout, and the Juju environment is '
180+ 'bootstrapped passing --debug')
181 # This is required by juju-core: see "juju help plugins".
182 parser.add_argument(
183 '--description', action=_DescriptionAction, default=argparse.SUPPRESS,
184@@ -184,13 +211,13 @@
185 print('juju quickstart v{}'.format(version))
186 print('bootstrapping the {} environment (type: {})'.format(
187 options.env_name, options.env_type))
188- app.bootstrap(options.env_name)
189+ app.bootstrap(options.env_name, debug=options.debug)
190 print('retrieving the Juju API address')
191 api_url = app.get_api_url(options.env_name)
192 print('connecting to {}'.format(api_url))
193 env = app.connect(api_url, options.admin_secret)
194 print('requesting Juju GUI deployment')
195- app.deploy_gui(env, 'juju-gui')
196+ app.deploy_gui(env, 'juju-gui', charm_url=options.charm_url)
197 print('Juju GUI deployment request accepted')
198 address = app.watch(env, 'juju-gui')
199 url = 'https://{}'.format(address)
200@@ -208,7 +235,6 @@
201 gui_api_url, options.admin_secret,
202 options.bundle_yaml, options.bundle_name)
203
204- # XXX 2013-10-18 frankban:
205- # Add a command line option to decline opening the browser.
206- webbrowser.open(url)
207+ if options.open_browser:
208+ webbrowser.open(url)
209 print('done!')
210
211=== modified file 'quickstart/tests/helpers.py'
212--- quickstart/tests/helpers.py 2013-10-30 17:37:46 +0000
213+++ quickstart/tests/helpers.py 2013-11-06 17:54:24 +0000
214@@ -18,6 +18,7 @@
215
216 from contextlib import contextmanager
217 import os
218+import shutil
219 import tempfile
220
221 import mock
222@@ -45,6 +46,14 @@
223 'bundle2': {'services': {'django': {}, 'nodejs': {}}},
224 })
225
226+ def _write_bundle_file(self, bundle_file, contents):
227+ """Parse and write contents into the given bundle file object."""
228+ if contents is None:
229+ contents = self.valid_bundle
230+ elif isinstance(contents, dict):
231+ contents = yaml.safe_dump(contents)
232+ bundle_file.write(contents)
233+
234 def make_bundle_file(self, contents=None):
235 """Create a Juju bundle file containing the given contents.
236
237@@ -52,16 +61,28 @@
238 self.valid_bundle.
239 Return the bundle file path.
240 """
241- if contents is None:
242- contents = self.valid_bundle
243- elif isinstance(contents, dict):
244- contents = yaml.safe_dump(contents)
245 bundle_file = tempfile.NamedTemporaryFile(delete=False)
246 self.addCleanup(os.remove, bundle_file.name)
247- bundle_file.write(contents)
248+ self._write_bundle_file(bundle_file, contents)
249 bundle_file.close()
250 return bundle_file.name
251
252+ def make_bundle_dir(self, contents=None):
253+ """Create a Juju bundle directory including a bundles.yaml file.
254+
255+ The file will contain the given contents.
256+
257+ If contents is None, use the valid bundle contents defined in
258+ self.valid_bundle.
259+ Return the bundle directory path.
260+ """
261+ bundle_dir = tempfile.mkdtemp()
262+ self.addCleanup(shutil.rmtree, bundle_dir)
263+ bundle_path = os.path.join(bundle_dir, 'bundles.yaml')
264+ with open(bundle_path, 'w') as bundle_file:
265+ self._write_bundle_file(bundle_file, contents)
266+ return bundle_dir
267+
268
269 class CallTestsMixin(object):
270 """Easily use the quickstart.utils.call function."""
271
272=== modified file 'quickstart/tests/test_app.py'
273--- quickstart/tests/test_app.py 2013-10-30 17:37:46 +0000
274+++ quickstart/tests/test_app.py 2013-11-06 17:54:24 +0000
275@@ -28,6 +28,9 @@
276 from quickstart.tests import helpers
277
278
279+mock_print = mock.patch('__builtin__.print')
280+
281+
282 class TestProgramExit(unittest.TestCase):
283
284 def test_string_representation(self):
285@@ -72,6 +75,13 @@
286 'juju', 'status', '-e', self.env_name, '--format', 'yaml')
287 return [call for _ in range(number)]
288
289+ def make_side_effects(self):
290+ """Return the minimum number of side effects for a successful call."""
291+ return [
292+ (0, '', ''), # Add a bootstrap call.
293+ (0, self.make_status_output('started'), ''), # Add a status call.
294+ ]
295+
296 def assert_status_retried(self, side_effects):
297 """Ensure the "juju status" command is retried several times.
298
299@@ -85,16 +95,20 @@
300
301 def test_success(self):
302 # The environment is successfully bootstrapped.
303- side_effects = [
304- (0, '', ''), # Add a bootstrap call.
305- (0, self.make_status_output('started'), ''), # Add a status call.
306- ]
307- with self.patch_multiple_calls(side_effects) as mock_call:
308+ with self.patch_multiple_calls(self.make_side_effects()) as mock_call:
309 app.bootstrap(self.env_name)
310 mock_call.assert_has_calls([
311 mock.call('juju', 'bootstrap', '-e', self.env_name),
312 ] + self.make_status_calls(1))
313
314+ def test_success_debug(self):
315+ # The environment is successfully bootstrapped in debug mode.
316+ with self.patch_multiple_calls(self.make_side_effects()) as mock_call:
317+ app.bootstrap(self.env_name, debug=True)
318+ mock_call.assert_has_calls([
319+ mock.call('juju', 'bootstrap', '-e', self.env_name, '--debug'),
320+ ] + self.make_status_calls(1))
321+
322 def test_bootstrap_failure(self):
323 # A ProgramExit is raised if an error occurs while bootstrapping.
324 with self.patch_call(1, error='bad wolf') as mock_call:
325@@ -233,6 +247,7 @@
326 self.assertIs(error, context_manager.exception)
327
328
329+@mock_print
330 class TestDeployGui(ProgramExitTestsMixin, unittest.TestCase):
331
332 charm_url = 'cs:precise/juju-gui-42'
333@@ -244,18 +259,20 @@
334 mock_get_charm_url = mock.Mock(side_effect=side_effect)
335 return mock.patch('quickstart.utils.get_charm_url', mock_get_charm_url)
336
337- def test_deployment(self):
338+ def test_deployment(self, mock_print):
339 # The function correctly deploys and exposes the service, retrieving
340 # the charm URL from the charmworld API.
341 env = mock.Mock()
342 with self.patch_get_charm_url():
343 app.deploy_gui(env, 'my-gui')
344 env.assert_has_calls([
345- mock.call.deploy('my-gui', 'cs:precise/juju-gui-42', to=0),
346+ mock.call.deploy('my-gui', self.charm_url, to=0),
347 mock.call.expose('my-gui')
348 ])
349+ mock_print.assert_called_once_with(
350+ 'charm URL: {}'.format(self.charm_url))
351
352- def test_deployment_default_charm_url(self):
353+ def test_deployment_default_charm_url(self, mock_print):
354 # The function correctly deploys and exposes the service, even if it is
355 # not able to retrieve the charm URL from the charmworld API.
356 env = mock.Mock()
357@@ -268,8 +285,22 @@
358 mock.call.deploy('my-gui', app.DEFAULT_CHARM_URL, to=0),
359 mock.call.expose('my-gui')
360 ])
361-
362- def test_api_error(self):
363+ mock_print.assert_called_once_with(
364+ 'charm URL: {}'.format(app.DEFAULT_CHARM_URL))
365+
366+ def test_deployment_provided_charm_url(self, mock_print):
367+ # The function correctly deploys and exposes the service using a user
368+ # provided Juju GUI charm URL.
369+ env = mock.Mock()
370+ charm_url = 'cs:~juju-gui/precise/juju-gui-116'
371+ app.deploy_gui(env, 'my-gui', charm_url=charm_url)
372+ env.assert_has_calls([
373+ mock.call.deploy('my-gui', charm_url, to=0),
374+ mock.call.expose('my-gui')
375+ ])
376+ mock_print.assert_called_once_with('charm URL: {}'.format(charm_url))
377+
378+ def test_api_error(self, mock_print):
379 # A ProgramExit is raised if an error occurs in one of the API calls.
380 env = mock.Mock()
381 env.deploy.side_effect = self.make_env_error('service already exists')
382@@ -280,7 +311,7 @@
383 env.deploy.assert_called_once_with(
384 'another-gui', 'cs:precise/juju-gui-42', to=0)
385
386- def test_other_errors(self):
387+ def test_other_errors(self, mock_print):
388 # Any other errors occurred during the process are not trapped.
389 error = ValueError('explode!')
390 env = mock.Mock()
391@@ -294,7 +325,7 @@
392 self.assertIs(error, context_manager.exception)
393
394
395-@mock.patch('__builtin__.print')
396+@mock_print
397 class TestWatch(ProgramExitTestsMixin, unittest.TestCase):
398
399 address = 'unit.example.com'
400
401=== modified file 'quickstart/tests/test_manage.py'
402--- quickstart/tests/test_manage.py 2013-10-30 17:37:46 +0000
403+++ quickstart/tests/test_manage.py 2013-11-06 17:54:24 +0000
404@@ -19,6 +19,8 @@
405 import argparse
406 import logging
407 import os
408+import shutil
409+import tempfile
410 import unittest
411
412 import mock
413@@ -80,6 +82,17 @@
414 ['mysql', 'wordpress'], sorted(options.bundle_services))
415 self.assertEqual(open(bundle_file).read(), options.bundle_yaml)
416
417+ def test_resulting_options_from_dir(self):
418+ # The options object is correctly set up when a bundle dir is passed.
419+ bundle_dir = self.make_bundle_dir()
420+ options = self.make_options(bundle_dir, bundle_name='bundle1')
421+ manage._validate_bundle(options, self.parser)
422+ self.assertEqual('bundle1', options.bundle_name)
423+ self.assertEqual(
424+ ['mysql', 'wordpress'], sorted(options.bundle_services))
425+ expected = open(os.path.join(bundle_dir, 'bundles.yaml')).read()
426+ self.assertEqual(expected, options.bundle_yaml)
427+
428 def test_expand_user(self):
429 # The ~ construct is correctly expanded in the validation process.
430 bundle_file = self.make_bundle_file()
431@@ -104,6 +117,19 @@
432 )
433 self.parser.error.assert_called_once_with(expected)
434
435+ def test_bundle_dir_not_valid(self):
436+ # A parser error is invoked if the bundle dir does not contain the
437+ # bundles.yaml file.
438+ bundle_dir = tempfile.mkdtemp()
439+ self.addCleanup(shutil.rmtree, bundle_dir)
440+ options = self.make_options(bundle_dir)
441+ manage._validate_bundle(options, self.parser)
442+ expected = (
443+ 'unable to open bundle file: '
444+ "[Errno 2] No such file or directory: '{}/bundles.yaml'"
445+ ).format(bundle_dir)
446+ self.parser.error.assert_called_once_with(expected)
447+
448 def test_url_error(self):
449 # A parser error is invoked if the bundle cannot be fetched from the
450 # provided URL.
451@@ -283,9 +309,12 @@
452 """Set up the options to be passed to the run function."""
453 options = {
454 'admin_secret': 'Secret!',
455+ 'debug': False,
456+ 'charm_url': None,
457 'bundle': None,
458 'env_name': 'aws',
459 'env_type': 'ec2',
460+ 'open_browser': True,
461 }
462 options.update(kwargs)
463 return mock.Mock(**options)
464@@ -294,12 +323,13 @@
465 # The application runs correctly if no bundle is provided.
466 options = self.make_options()
467 manage.run(options)
468- mock_app.bootstrap.assert_called_once_with(options.env_name)
469+ mock_app.bootstrap.assert_called_once_with(
470+ options.env_name, debug=options.debug)
471 mock_app.get_api_url.assert_called_once_with(options.env_name)
472 mock_app.connect.assert_called_once_with(
473 mock_app.get_api_url(), options.admin_secret)
474 mock_app.deploy_gui.assert_called_once_with(
475- mock_app.connect(), 'juju-gui')
476+ mock_app.connect(), 'juju-gui', charm_url=options.charm_url)
477 mock_app.watch.assert_called_once_with(mock_app.connect(), 'juju-gui')
478 mock_open.assert_called_once_with(
479 'https://{}'.format(mock_app.watch()))
480@@ -315,3 +345,9 @@
481 mock_app.deploy_bundle.assert_called_once_with(
482 'wss://gui.example.com:443/ws', options.admin_secret,
483 'mybundle: contents', 'mybundle')
484+
485+ def test_no_browser(self, mock_app, mock_open):
486+ # It is possible to avoid opening the GUI in the browser.
487+ options = self.make_options(open_browser=False)
488+ manage.run(options)
489+ self.assertFalse(mock_open.called)
490
491=== modified file 'quickstart/tests/test_utils.py'
492--- quickstart/tests/test_utils.py 2013-10-31 08:33:49 +0000
493+++ quickstart/tests/test_utils.py 2013-11-06 17:54:24 +0000
494@@ -157,6 +157,14 @@
495 helpers.BundleFileTestsMixin, helpers.ValueErrorTestsMixin,
496 unittest.TestCase):
497
498+ def assert_bundle(
499+ self, expected_name, expected_services, contents,
500+ bundle_name=None):
501+ """Ensure parsing the given contents returns the expected values."""
502+ name, services = utils.parse_bundle(contents, bundle_name=bundle_name)
503+ self.assertEqual(expected_name, name)
504+ self.assertEqual(set(expected_services), set(services))
505+
506 def test_invalid_yaml(self):
507 # A ValueError is raised if the bundle contents are not a valid YAML.
508 with self.assertRaises(ValueError) as context_manager:
509@@ -230,15 +238,20 @@
510 contents = yaml.safe_dump({
511 'mybundle': {'services': {'wordpress': {}, 'mysql': {}}},
512 })
513- name, services = utils.parse_bundle(contents)
514- self.assertEqual('mybundle', name)
515- self.assertEqual(['mysql', 'wordpress'], sorted(services))
516+ self.assert_bundle('mybundle', ['mysql', 'wordpress'], contents)
517
518 def test_success_multiple_bundles(self):
519 # The function succeeds with multiple bundles.
520- name, services = utils.parse_bundle(self.valid_bundle, 'bundle2')
521- self.assertEqual('bundle2', name)
522- self.assertEqual(['django', 'nodejs'], sorted(services))
523+ self.assert_bundle(
524+ 'bundle2', ['django', 'nodejs'], self.valid_bundle, 'bundle2')
525+
526+ def test_success_json(self):
527+ # Since JSON is a subset of YAML, the function also support JSON
528+ # encoded bundles.
529+ contents = json.dumps({
530+ 'mybundle': {'services': {'wordpress': {}, 'mysql': {}}},
531+ })
532+ self.assert_bundle('mybundle', ['mysql', 'wordpress'], contents)
533
534
535 class TestParseEnvFile(
536
537=== modified file 'quickstart/utils.py'
538--- quickstart/utils.py 2013-10-31 08:33:49 +0000
539+++ quickstart/utils.py 2013-11-06 17:54:24 +0000
540@@ -101,7 +101,10 @@
541
542
543 def parse_bundle(bundle_yaml, bundle_name=None):
544- """Parse the provided bundle YAML decoded contents.
545+ """Parse the provided bundle YAML encoded contents.
546+
547+ Since a valid JSON is a subset of YAML this function can be used also to
548+ parse JSON encoded contents.
549
550 Return a tuple containing the bundle name and the list of services included
551 in the bundle.
552
553=== modified file 'setup.py'
554--- setup.py 2013-10-30 10:16:36 +0000
555+++ setup.py 2013-11-06 17:54:24 +0000
556@@ -57,7 +57,7 @@
557 long_description=open(description_path).read(),
558 author='The Juju GUI team',
559 author_email='juju-gui@lists.ubuntu.com',
560- url='https://launchpad.net/juju-gui/juju-quickstart',
561+ url='https://launchpad.net/juju-quickstart',
562 keywords='juju quickstart plugin',
563 packages=find_packages(),
564 package_data={PROJECT_NAME: data_files},

Subscribers

People subscribed via source and target branches