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

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 59
Proposed branch: lp:~frankban/juju-quickstart/bundle-urls
Merge into: lp:juju-quickstart
Diff against target: 440 lines (+226/-47)
7 files modified
README.rst (+2/-2)
quickstart/__init__.py (+24/-1)
quickstart/manage.py (+51/-25)
quickstart/settings.py (+4/-1)
quickstart/tests/test_manage.py (+23/-0)
quickstart/tests/test_utils.py (+103/-16)
quickstart/utils.py (+19/-2)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/bundle-urls
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+211057@code.launchpad.net

Description of the change

Improve bundle URLs support.

Add support for promulgated bundle short names.
Add support for jujucharms bundle URLs.
Improve help messages.

Tests: `make check`.

QA:

Run `.venv/bin/python juju-quickstart -h`
and please check the spelling of the whole
help message. Also ensure it is nicely
printed on a small terminal (e.g. 80x24).

Now let's check bundle errors: all the commands
below should return a pertinent error message:

.venv/bin/python juju-quickstart bundle:
.venv/bin/python juju-quickstart https://jujucharms.com/bundle/mediawiki/single/wtf
.venv/bin/python juju-quickstart https://jujucharms.com/bundle/mediawiki/double
.venv/bin/python juju-quickstart https://jujucharms.com/charms/mediawiki/single
.venv/bin/python juju-quickstart bundle:mediawiki/42/single
.venv/bin/python juju-quickstart bundle:~frankban/mediawiki/single

Deploy bundles, destroy the environment after each command.

Deploy the promulgated mediawiki single bundle:
.venv/bin/python juju-quickstart bundle:~charmers/mediawiki/6/single

Deploy the mediawiki scalable bundle:
.venv/bin/python juju-quickstart bundle:mediawiki/scalable

Deploy a bundle using its jujucharms URLs:
.venv/bin/python juju-quickstart https://jujucharms.com/bundle/~bac/charmworld-demo/charmworld-minimal/

Deploy a bundle using direct HTTPS:
.venv/bin/python juju-quickstart https://raw.github.com/castrojo/mongodb-bundle/master/bundles.yaml

https://codereview.appspot.com/75830045/

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

Reviewers: mp+211057_code.launchpad.net,

Message:
Please take a look.

Description:
Improve bundle URLs support.

Add support for promulgated bundle short names.
Add support for jujucharms bundle URLs.
Improve help messages.

Tests: `make check`.

QA:

Run `.venv/bin/python juju-quickstart -h`
and please check the spelling of the whole
help message. Also ensure it is nicely
printed on a small terminal (e.g. 80x24).

Now let's check bundle errors: all the commands
below should return a pertinent error message:

.venv/bin/python juju-quickstart bundle:
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/mediawiki/single/wtf
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/mediawiki/double
.venv/bin/python juju-quickstart
https://jujucharms.com/charms/mediawiki/single
.venv/bin/python juju-quickstart bundle:mediawiki/42/single
.venv/bin/python juju-quickstart bundle:~frankban/mediawiki/single

Deploy bundles, destroy the environment after each command.

Deploy the promulgated mediawiki single bundle:
.venv/bin/python juju-quickstart bundle:~charmers/mediawiki/6/single

Deploy the mediawiki scalable bundle:
.venv/bin/python juju-quickstart bundle:mediawiki/scalable

Deploy a bundle using its jujucharms URLs:
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/~bac/charmworld-demo/charmworld-minimal/

Deploy a bundle using direct HTTPS:
.venv/bin/python juju-quickstart
https://raw.github.com/castrojo/mongodb-bundle/master/bundles.yaml

https://code.launchpad.net/~frankban/juju-quickstart/bundle-urls/+merge/211057

(do not edit description out of merge proposal)

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

Affected files (+229, -46 lines):
   M README.rst
   A [revision details]
   M quickstart/__init__.py
   M quickstart/manage.py
   M quickstart/settings.py
   M quickstart/tests/test_manage.py
   M quickstart/tests/test_utils.py
   M quickstart/utils.py

Revision history for this message
Richard Harding (rharding) wrote :

Thanks for this, mainly reviewed the docs. A couple of suggestions. Let
me know if any of the comments are unclear.

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py
File quickstart/__init__.py (right):

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode34
quickstart/__init__.py:34: * Bundles can be deployed, from local files,
HTTP(S) URLs or the charm store,
URLs <comma>

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode37
quickstart/__init__.py:37: into the GUI, to observe and manage the
environment visually.
into the Juju GUI. (I'd drop the rest)

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

https://codereview.appspot.com/75830045/diff/1/quickstart/manage.py#newcode332
quickstart/manage.py:332: '1) a fully qualified bundle URL, starting
with "bundle:"\n'
Can we word this to start with the easy example that's recommended by
moving the promulgated bundle up here.

Then word it more that "You can also specify a user bundle, and a
specific revision...

This way we push the promoted and simple case and fall back to any
person's work.

https://codereview.appspot.com/75830045/

Revision history for this message
Brad Crittenden (bac) wrote :

Code LGTM

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py
File quickstart/__init__.py (right):

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode33
quickstart/__init__.py:33: (installing on an existing state server when
possible).
When not possible isn't there an additional machine?

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

https://codereview.appspot.com/75830045/diff/1/quickstart/manage.py#newcode72
quickstart/manage.py:72: # remote location.
I'd move this second comment to the actual location it happens.

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

https://codereview.appspot.com/75830045/diff/1/quickstart/tests/test_manage.py#newcode112
quickstart/tests/test_manage.py:112: # The options object is correctly
set up when a jujucharms bumdle URL
typo: bundle

https://codereview.appspot.com/75830045/

64. By Francesco Banconi

Changes as per review.

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

Please take a look.

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py
File quickstart/__init__.py (right):

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode33
quickstart/__init__.py:33: (installing on an existing state server when
possible).
On 2014/03/14 14:51:20, bac wrote:
> When not possible isn't there an additional machine?

Yes, when using local envs a new machine is created. Not sure if we
should specify it in this context.

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode34
quickstart/__init__.py:34: * Bundles can be deployed, from local files,
HTTP(S) URLs or the charm store,
On 2014/03/14 14:37:08, rharding wrote:
> URLs <comma>

Done.

https://codereview.appspot.com/75830045/diff/1/quickstart/__init__.py#newcode37
quickstart/__init__.py:37: into the GUI, to observe and manage the
environment visually.
On 2014/03/14 14:37:08, rharding wrote:
> into the Juju GUI. (I'd drop the rest)

Done.

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

https://codereview.appspot.com/75830045/diff/1/quickstart/manage.py#newcode72
quickstart/manage.py:72: # remote location.
On 2014/03/14 14:51:20, bac wrote:
> I'd move this second comment to the actual location it happens.

Done.

https://codereview.appspot.com/75830045/diff/1/quickstart/manage.py#newcode332
quickstart/manage.py:332: '1) a fully qualified bundle URL, starting
with "bundle:"\n'
On 2014/03/14 14:37:08, rharding wrote:
> Can we word this to start with the easy example that's recommended by
moving the
> promulgated bundle up here.

> Then word it more that "You can also specify a user bundle, and a
specific
> revision...

> This way we push the promoted and simple case and fall back to any
person's
> work.

Done.

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

https://codereview.appspot.com/75830045/diff/1/quickstart/tests/test_manage.py#newcode112
quickstart/tests/test_manage.py:112: # The options object is correctly
set up when a jujucharms bumdle URL
On 2014/03/14 14:51:20, bac wrote:
> typo: bundle

Done.

https://codereview.appspot.com/75830045/

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

*** Submitted:

Improve bundle URLs support.

Add support for promulgated bundle short names.
Add support for jujucharms bundle URLs.
Improve help messages.

Tests: `make check`.

QA:

Run `.venv/bin/python juju-quickstart -h`
and please check the spelling of the whole
help message. Also ensure it is nicely
printed on a small terminal (e.g. 80x24).

Now let's check bundle errors: all the commands
below should return a pertinent error message:

.venv/bin/python juju-quickstart bundle:
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/mediawiki/single/wtf
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/mediawiki/double
.venv/bin/python juju-quickstart
https://jujucharms.com/charms/mediawiki/single
.venv/bin/python juju-quickstart bundle:mediawiki/42/single
.venv/bin/python juju-quickstart bundle:~frankban/mediawiki/single

Deploy bundles, destroy the environment after each command.

Deploy the promulgated mediawiki single bundle:
.venv/bin/python juju-quickstart bundle:~charmers/mediawiki/6/single

Deploy the mediawiki scalable bundle:
.venv/bin/python juju-quickstart bundle:mediawiki/scalable

Deploy a bundle using its jujucharms URLs:
.venv/bin/python juju-quickstart
https://jujucharms.com/bundle/~bac/charmworld-demo/charmworld-minimal/

Deploy a bundle using direct HTTPS:
.venv/bin/python juju-quickstart
https://raw.github.com/castrojo/mongodb-bundle/master/bundles.yaml

R=rharding, bac
CC=
https://codereview.appspot.com/75830045

https://codereview.appspot.com/75830045/

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
=== modified file 'README.rst'
--- README.rst 2014-01-29 15:25:06 +0000
+++ README.rst 2014-03-14 18:27:29 +0000
@@ -13,14 +13,14 @@
13 session.13 session.
14* The Juju GUI is automatically installed, adding no additional machines14* The Juju GUI is automatically installed, adding no additional machines
15 (installing on an existing state server when possible).15 (installing on an existing state server when possible).
16* Bundles can be deployed, from local files, HTTP(S) URLs or the charm store,16* Bundles can be deployed, from local files, HTTP(S) URLs, or the charm store,
17 so that a complete topology of services can be set up in one simple command.17 so that a complete topology of services can be set up in one simple command.
18* Quickstart ends by opening the browser and automatically logging the user18* Quickstart ends by opening the browser and automatically logging the user
19 into the GUI, to observe and manage the environment visually.19 into the GUI, to observe and manage the environment visually.
20* Users with a running Juju environment can run the quickstart command again to20* Users with a running Juju environment can run the quickstart command again to
21 simply re-open the GUI without having to find the proper URL and password.21 simply re-open the GUI without having to find the proper URL and password.
2222
23To install and start Juju Quickstart, run the following::23To start Juju Quickstart, run the following::
2424
25 juju-quickstart [-i]25 juju-quickstart [-i]
2626
2727
=== modified file 'quickstart/__init__.py'
--- quickstart/__init__.py 2014-03-13 11:56:58 +0000
+++ quickstart/__init__.py 2014-03-14 18:27:29 +0000
@@ -22,7 +22,30 @@
22from __future__ import unicode_literals22from __future__ import unicode_literals
2323
2424
25VERSION = (1, 1, 3)25FEATURES = """
26Features include the following:
27
28* New users are guided, as needed, to install Juju, set up SSH keys, and
29 configure it for first use.
30* Juju environments can be created and managed from a command line interactive
31 session.
32* The Juju GUI is automatically installed, adding no additional machines
33 (installing on an existing state server when possible).
34* Bundles can be deployed, from local files, HTTP(S) URLs, or the charm store,
35 so that a complete topology of services can be set up in one simple command.
36* Quickstart ends by opening the browser and automatically logging the user
37 into the Juju GUI.
38* Users with a running Juju environment can run the quickstart command again to
39 simply re-open the GUI without having to find the proper URL and password.
40
41To start Juju Quickstart, run the following:
42
43 juju-quickstart [-i]
44
45Once Juju has been installed, the command can also be run as a juju plugin,
46without the hyphen ("juju quickstart").
47"""
48VERSION = (1, 2, 0)
2649
2750
28def get_version():51def get_version():
2952
=== modified file 'quickstart/manage.py'
--- quickstart/manage.py 2014-03-11 12:47:44 +0000
+++ quickstart/manage.py 2014-03-14 18:27:29 +0000
@@ -65,13 +65,15 @@
65 """65 """
66 bundle = options.bundle66 bundle = options.bundle
67 bundle_id = None67 bundle_id = None
68 if bundle.startswith('bundle:'):68 jujucharms_prefix = settings.JUJUCHARMS_BUNDLE_URL
69 # Convert "bundle:" URLs into HTTPS ones. The next if block below will69 if bundle.startswith('bundle:') or bundle.startswith(jujucharms_prefix):
70 # then load the bundle contents from the remote location.70 # Convert "bundle:" or jujucharms.com URLs into Charmworld HTTPS ones.
71 try:71 try:
72 bundle, bundle_id = utils.convert_bundle_url(bundle)72 bundle, bundle_id = utils.convert_bundle_url(bundle)
73 except ValueError as err:73 except ValueError as err:
74 return parser.error('unable to open the bundle: {}'.format(err))74 return parser.error('unable to open the bundle: {}'.format(err))
75 # The next if block below will then load the bundle contents from the
76 # remote location.
75 if bundle.startswith('http://') or bundle.startswith('https://'):77 if bundle.startswith('http://') or bundle.startswith('https://'):
76 # Load the bundle from a remote URL.78 # Load the bundle from a remote URL.
77 try:79 try:
@@ -319,47 +321,66 @@
319 if default_env_name is not None:321 if default_env_name is not None:
320 env_help = '{} (%(default)s)'.format(env_help)322 env_help = '{} (%(default)s)'.format(env_help)
321 # Create and set up the arguments parser.323 # Create and set up the arguments parser.
322 parser = argparse.ArgumentParser(description=quickstart.__doc__)324 parser = argparse.ArgumentParser(
325 description=quickstart.__doc__, epilog=quickstart.FEATURES,
326 formatter_class=argparse.RawTextHelpFormatter)
327 # Note: since we use the RawTextHelpFormatter, when adding/changing options
328 # make sure the help text is nicely displayed on small 80 columns terms.
323 parser.add_argument(329 parser.add_argument(
324 'bundle', default=None, nargs='?',330 'bundle', default=None, nargs='?',
325 help='The optional bundle to be deployed. The bundle can be '331 help='The optional bundle to be deployed. The bundle can be:\n'
326 '1) a fully qualified bundle URL (starting with "bundle:"), '332 '1) a fully qualified bundle URL, starting with "bundle:"\n'
327 '2) a URL ("http:" or "https:") to a YAML/JSON, '333 ' e.g. "bundle:mediawiki/single".\n'
328 '3) a path to a YAML/JSON file, or '334 ' Non promulgated bundles can be requested providing\n'
329 '4) a path to a directory containing a "bundles.yaml" file')335 ' the user, e.g. "bundle:~user/mediawiki/single".\n'
336 ' A specific bundle revision can also be requested,\n'
337 ' e.g. "bundle:~myuser/mediawiki/42/single".\n'
338 ' If not specified, the last bundle revision is used;\n'
339 '2) a jujucharms bundle URL, starting with\n'
340 ' "{jujucharm}", e.g.\n'
341 ' "{jujucharm}~user/wiki/1/simple/".\n'
342 ' As seen above, jujucharms bundle URLs can also be\n'
343 ' shortened, e.g.\n'
344 ' "{jujucharm}mediawiki/scalable/";\n'
345 '3) a URL ("http:" or "https:") to a YAML/JSON, e.g.\n'
346 ' "https://raw.github.com/user/my/master/bundles.yaml";\n'
347 '4) a local path to a YAML/JSON file;\n'
348 '5) a path to a directory containing a "bundles.yaml"\n'
349 ' file'.format(jujucharm=settings.JUJUCHARMS_BUNDLE_URL))
330 parser.add_argument(350 parser.add_argument(
331 '-e', '--environment', default=default_env_name, dest='env_name',351 '-e', '--environment', default=default_env_name, dest='env_name',
332 help=env_help)352 help=env_help)
333 parser.add_argument(353 parser.add_argument(
334 '-n', '--bundle-name', default=None, dest='bundle_name',354 '-n', '--bundle-name', default=None, dest='bundle_name',
335 help='The name of the bundle to use. This must be included in the '355 help='The name of the bundle to use.\n'
336 'provided bundle YAML/JSON. Specifying the bundle name is not '356 'This must be included in the provided bundle YAML/JSON.\n'
337 'required if the bundle YAML/JSON only contains one bundle. This '357 'Specifying the bundle name is not required if the\n'
338 'option is ignored if the bundle file is not specified')358 'bundle YAML/JSON only contains one bundle. This option\n'
359 'is ignored if the bundle file is not specified')
339 parser.add_argument(360 parser.add_argument(
340 '-i', '--interactive', action='store_true', dest='interactive',361 '-i', '--interactive', action='store_true', dest='interactive',
341 help='Start the environments management interactive session')362 help='Start the environments management interactive session')
342 parser.add_argument(363 parser.add_argument(
343 '--environments-file', dest='env_file',364 '--environments-file', dest='env_file',
344 default=os.path.join(settings.JUJU_HOME, 'environments.yaml'),365 default=os.path.join(settings.JUJU_HOME, 'environments.yaml'),
345 help='The path to the Juju environments YAML file (%(default)s)')366 help='The path to the Juju environments YAML file\n(%(default)s)')
346 parser.add_argument(367 parser.add_argument(
347 '--gui-charm-url', dest='charm_url',368 '--gui-charm-url', dest='charm_url',
348 help='The Juju GUI charm URL to deploy in the environment. If not '369 help='The Juju GUI charm URL to deploy in the environment.\n'
349 'provided, the last release of the GUI will be deployed. The '370 'If not provided, the last release of the GUI will be\n'
350 'charm URL must include the charm version, e.g. '371 'deployed. The charm URL must include the charm version,\n'
351 'cs:~juju-gui/precise/juju-gui-116. This option is ignored if '372 'e.g. "cs:~juju-gui/precise/juju-gui-162". This option is\n'
352 'Juju GUI is already present in the environment')373 'ignored if the GUI is already present in the environment')
353 parser.add_argument(374 parser.add_argument(
354 '--no-browser', action='store_false', dest='open_browser',375 '--no-browser', action='store_false', dest='open_browser',
355 help='Avoid opening the browser to the GUI at the end of the process')376 help='Avoid opening the browser to the GUI at the end of the\nprocess')
356 parser.add_argument(377 parser.add_argument(
357 '--version', action='version', version='%(prog)s {}'.format(version))378 '--version', action='version', version='%(prog)s {}'.format(version))
358 parser.add_argument(379 parser.add_argument(
359 '--debug', action='store_true',380 '--debug', action='store_true',
360 help='Turn debug mode on. When enabled, all the subcommands and API '381 help='Turn debug mode on. When enabled, all the subcommands\n'
361 'calls are logged to stdout, and the Juju environment is '382 'and API calls are logged to stdout, and the Juju\n'
362 'bootstrapped passing --debug')383 'environment is bootstrapped passing --debug')
363 # This is required by juju-core: see "juju help plugins".384 # This is required by juju-core: see "juju help plugins".
364 parser.add_argument(385 parser.add_argument(
365 '--description', action=_DescriptionAction, default=argparse.SUPPRESS,386 '--description', action=_DescriptionAction, default=argparse.SUPPRESS,
@@ -382,6 +403,10 @@
382def run(options):403def run(options):
383 """Run the application."""404 """Run the application."""
384 print('juju quickstart v{}'.format(version))405 print('juju quickstart v{}'.format(version))
406 if options.bundle is not None:
407 print('contents loaded for bundle {} (services: {})'.format(
408 options.bundle_name, len(options.bundle_services)))
409
385 logging.debug('ensuring juju and lxc are installed')410 logging.debug('ensuring juju and lxc are installed')
386 juju_version = app.ensure_dependencies()411 juju_version = app.ensure_dependencies()
387412
@@ -431,7 +456,7 @@
431 address = app.watch(env, unit_name)456 address = app.watch(env, unit_name)
432 env.close()457 env.close()
433 url = 'https://{}'.format(address)458 url = 'https://{}'.format(address)
434 print('url: {}\npassword: {}'.format(url, admin_secret))459 print('\nJuju GUI URL: {}\npassword: {}\n'.format(url, admin_secret))
435 gui_api_url = 'wss://{}:443/ws'.format(address)460 gui_api_url = 'wss://{}:443/ws'.format(address)
436 print('connecting to the Juju GUI server')461 print('connecting to the Juju GUI server')
437 gui_env = app.connect(gui_api_url, admin_secret)462 gui_env = app.connect(gui_api_url, admin_secret)
@@ -447,7 +472,8 @@
447 app.deploy_bundle(472 app.deploy_bundle(
448 gui_env, options.bundle_yaml, options.bundle_name,473 gui_env, options.bundle_yaml, options.bundle_name,
449 options.bundle_id)474 options.bundle_id)
450 print('bundle deployment request accepted')475 print('bundle deployment request accepted\n'
476 'use the GUI to check the bundle deployment progress')
451477
452 if options.open_browser:478 if options.open_browser:
453 token = app.create_auth_token(gui_env)479 token = app.create_auth_token(gui_env)
454480
=== modified file 'quickstart/settings.py'
--- quickstart/settings.py 2014-03-12 10:31:34 +0000
+++ quickstart/settings.py 2014-03-14 18:27:29 +0000
@@ -26,11 +26,14 @@
2626
27# The default Juju GUI charm URL to use when it is not possible to retrieve it27# The default Juju GUI charm URL to use when it is not possible to retrieve it
28# from the charmworld API, e.g. due to temporary connection/charmworld errors.28# from the charmworld API, e.g. due to temporary connection/charmworld errors.
29DEFAULT_CHARM_URL = 'cs:precise/juju-gui-85'29DEFAULT_CHARM_URL = 'cs:precise/juju-gui-86'
3030
31# The quickstart app short description.31# The quickstart app short description.
32DESCRIPTION = 'set up a Juju environment (including the GUI) in very few steps'32DESCRIPTION = 'set up a Juju environment (including the GUI) in very few steps'
3333
34# The URL namespace for bundles in jujucharms.com.
35JUJUCHARMS_BUNDLE_URL = 'https://jujucharms.com/bundle/'
36
34# The path to the Juju command.37# The path to the Juju command.
35JUJU_CMD = '/usr/bin/juju'38JUJU_CMD = '/usr/bin/juju'
3639
3740
=== modified file 'quickstart/tests/test_manage.py'
--- quickstart/tests/test_manage.py 2014-03-11 12:34:06 +0000
+++ quickstart/tests/test_manage.py 2014-03-14 18:27:29 +0000
@@ -108,6 +108,21 @@
108 ['mysql', 'wordpress'], sorted(options.bundle_services))108 ['mysql', 'wordpress'], sorted(options.bundle_services))
109 self.assertEqual(open(bundle_file).read(), options.bundle_yaml)109 self.assertEqual(open(bundle_file).read(), options.bundle_yaml)
110110
111 def test_resulting_options_from_jujucharms_url(self):
112 # The options object is correctly set up when a jujucharms bundle URL
113 # is passed.
114 bundle_file = self.make_bundle_file()
115 url = settings.JUJUCHARMS_BUNDLE_URL + 'my/bundle/'
116 options = self.make_options(url, bundle_name='bundle1')
117 with self.patch_urlread(contents=self.valid_bundle) as mock_urlread:
118 manage._validate_bundle(options, self.parser)
119 mock_urlread.assert_called_once_with(
120 'https://manage.jujucharms.com/bundle/~charmers/my/bundle/json')
121 self.assertEqual('bundle1', options.bundle_name)
122 self.assertEqual(
123 ['mysql', 'wordpress'], sorted(options.bundle_services))
124 self.assertEqual(open(bundle_file).read(), options.bundle_yaml)
125
111 def test_resulting_options_from_dir(self):126 def test_resulting_options_from_dir(self):
112 # The options object is correctly set up when a bundle dir is passed.127 # The options object is correctly set up when a bundle dir is passed.
113 bundle_dir = self.make_bundle_dir()128 bundle_dir = self.make_bundle_dir()
@@ -175,6 +190,14 @@
175 self.parser.error.assert_called_once_with(190 self.parser.error.assert_called_once_with(
176 'unable to open the bundle: invalid bundle URL: bundle:')191 'unable to open the bundle: invalid bundle URL: bundle:')
177192
193 def test_jujucharms_url_error(self):
194 # A parser error is invoked if an invalid jujucharms URL is provided.
195 url = settings.JUJUCHARMS_BUNDLE_URL + 'no-such'
196 options = self.make_options(url)
197 manage._validate_bundle(options, self.parser)
198 self.parser.error.assert_called_once_with(
199 'unable to open the bundle: invalid bundle URL: {}'.format(url))
200
178 def test_error_parsing_bundle_contents(self):201 def test_error_parsing_bundle_contents(self):
179 # A parser error is invoked if an error occurs parsing the bundle YAML.202 # A parser error is invoked if an error occurs parsing the bundle YAML.
180 bundle_file = self.make_bundle_file()203 bundle_file = self.make_bundle_file()
181204
=== modified file 'quickstart/tests/test_utils.py'
--- quickstart/tests/test_utils.py 2014-03-11 12:34:06 +0000
+++ quickstart/tests/test_utils.py 2014-03-14 18:27:29 +0000
@@ -189,28 +189,115 @@
189189
190class TestConvertBundleUrl(helpers.ValueErrorTestsMixin, unittest.TestCase):190class TestConvertBundleUrl(helpers.ValueErrorTestsMixin, unittest.TestCase):
191191
192 def test_conversion(self):192 def test_full_bundle_url(self):
193 # The HTTPS location to the YAML contents are correctly returned.193 # The HTTPS location to the YAML contents is correctly returned.
194 bundle_url = 'bundle:~myuser/wiki-bundle/42/wiki'194 bundle_url = 'bundle:~myuser/wiki-bundle/42/wiki'
195 expected = (195 url, bundle_id = utils.convert_bundle_url(bundle_url)
196 'https://manage.jujucharms.com/bundle/'196 self.assertEqual(
197 '~myuser/wiki-bundle/42/wiki/json',197 'https://manage.jujucharms.com'
198 '~myuser/wiki-bundle/42/wiki',198 '/bundle/~myuser/wiki-bundle/42/wiki/json', url)
199 )199 self.assertEqual('~myuser/wiki-bundle/42/wiki', bundle_id)
200 self.assertEqual(expected, utils.convert_bundle_url(bundle_url))
201200
202 def test_right_strip(self):201 def test_bundle_url_right_strip(self):
203 # The trailing slash in the bundle URL is removed.202 # The trailing slash in the bundle URL is removed.
204 bundle_url = 'bundle:~myuser/wiki-bundle/42/wiki/'203 bundle_url = 'bundle:~myuser/wiki-bundle/42/wiki/'
205 expected = ('https://manage.jujucharms.com'204 url, bundle_id = utils.convert_bundle_url(bundle_url)
206 '/bundle/~myuser/wiki-bundle/42/wiki/json',205 self.assertEqual(
207 '~myuser/wiki-bundle/42/wiki')206 'https://manage.jujucharms.com'
208 self.assertEqual(expected, utils.convert_bundle_url(bundle_url))207 '/bundle/~myuser/wiki-bundle/42/wiki/json', url)
208 self.assertEqual('~myuser/wiki-bundle/42/wiki', bundle_id)
209
210 def test_bundle_url_no_revision(self):
211 # The bundle revision is optional.
212 bundle_url = 'bundle:~myuser/wiki-bundle/wiki-simple'
213 url, bundle_id = utils.convert_bundle_url(bundle_url)
214 self.assertEqual(
215 'https://manage.jujucharms.com'
216 '/bundle/~myuser/wiki-bundle/wiki-simple/json', url)
217 self.assertEqual('~myuser/wiki-bundle/wiki-simple', bundle_id)
218
219 def test_bundle_url_no_user(self):
220 # If the bundle user is not specified, the bundle is assumed to be
221 # promulgated and owned by "charmers".
222 bundle_url = 'bundle:wiki-bundle/1/wiki'
223 url, bundle_id = utils.convert_bundle_url(bundle_url)
224 self.assertEqual(
225 'https://manage.jujucharms.com'
226 '/bundle/~charmers/wiki-bundle/1/wiki/json', url)
227 self.assertEqual('~charmers/wiki-bundle/1/wiki', bundle_id)
228
229 def test_bundle_url_short_form(self):
230 # A promulgated bundle URL can just include the basket and the name.
231 bundle_url = 'bundle:wiki-bundle/wiki'
232 url, bundle_id = utils.convert_bundle_url(bundle_url)
233 self.assertEqual(
234 'https://manage.jujucharms.com'
235 '/bundle/~charmers/wiki-bundle/wiki/json', url)
236 self.assertEqual('~charmers/wiki-bundle/wiki', bundle_id)
237
238 def test_full_jujucharms_url(self):
239 # The HTTPS location to the YAML contents is correctly returned.
240 url, bundle_id = utils.convert_bundle_url(
241 settings.JUJUCHARMS_BUNDLE_URL + '~myuser/wiki-bundle/42/wiki')
242 self.assertEqual(
243 'https://manage.jujucharms.com'
244 '/bundle/~myuser/wiki-bundle/42/wiki/json', url)
245 self.assertEqual('~myuser/wiki-bundle/42/wiki', bundle_id)
246
247 def test_jujucharms_url_right_strip(self):
248 # The trailing slash in the jujucharms URL is removed.
249 url, bundle_id = utils.convert_bundle_url(
250 settings.JUJUCHARMS_BUNDLE_URL + '~charmers/mediawiki/6/scalable/')
251 self.assertEqual(
252 'https://manage.jujucharms.com'
253 '/bundle/~charmers/mediawiki/6/scalable/json', url)
254 self.assertEqual('~charmers/mediawiki/6/scalable', bundle_id)
255
256 def test_jujucharms_url_no_revision(self):
257 # The bundle revision is optional.
258 url, bundle_id = utils.convert_bundle_url(
259 settings.JUJUCHARMS_BUNDLE_URL + '~myuser/wiki/wiki-simple/')
260 self.assertEqual(
261 'https://manage.jujucharms.com'
262 '/bundle/~myuser/wiki/wiki-simple/json', url)
263 self.assertEqual('~myuser/wiki/wiki-simple', bundle_id)
264
265 def test_jujucharms_url_no_user(self):
266 # If the bundle user is not specified, the bundle is assumed to be
267 # promulgated and owned by "charmers".
268 url, bundle_id = utils.convert_bundle_url(
269 settings.JUJUCHARMS_BUNDLE_URL + 'mediawiki/42/single/')
270 self.assertEqual(
271 'https://manage.jujucharms.com'
272 '/bundle/~charmers/mediawiki/42/single/json', url)
273 self.assertEqual('~charmers/mediawiki/42/single', bundle_id)
274
275 def test_jujucharms_url_short_form(self):
276 # A jujucharms URL for a promulgated bundle can just include the basket
277 # and the name.
278 url, bundle_id = utils.convert_bundle_url(
279 settings.JUJUCHARMS_BUNDLE_URL + 'wiki-bundle/wiki/')
280 self.assertEqual(
281 'https://manage.jujucharms.com'
282 '/bundle/~charmers/wiki-bundle/wiki/json', url)
283 self.assertEqual('~charmers/wiki-bundle/wiki', bundle_id)
209284
210 def test_error(self):285 def test_error(self):
211 # A ValueError is raised if the given bundle URL is not valid.286 # A ValueError is raised if the bundle/jujucharms URL is not valid.
212 with self.assert_value_error('invalid bundle URL: bundle:'):287 bad_urls = (
213 utils.convert_bundle_url('bundle:')288 'bad', 'bundle:', 'bundle:~user', 'bundle:no-such',
289 'bundle:~user/name', 'bundle:~user/basket/revision/name',
290 'bundle:basket/name//', 'bundle:basket.name/bundle.name',
291 settings.JUJUCHARMS_BUNDLE_URL,
292 settings.JUJUCHARMS_BUNDLE_URL + 'bad',
293 settings.JUJUCHARMS_BUNDLE_URL + '~user/no-such',
294 settings.JUJUCHARMS_BUNDLE_URL + '~user/basket/revision/name/',
295 settings.JUJUCHARMS_BUNDLE_URL + '~user/basket/42/name/error',
296 'https://jujucharms.com/charms/mediawiki/simple/',
297 )
298 for url in bad_urls:
299 with self.assert_value_error('invalid bundle URL: {}'.format(url)):
300 utils.convert_bundle_url(url)
214301
215302
216class TestGetCharmUrl(helpers.UrlReadTestsMixin, unittest.TestCase):303class TestGetCharmUrl(helpers.UrlReadTestsMixin, unittest.TestCase):
217304
=== modified file 'quickstart/utils.py'
--- quickstart/utils.py 2014-03-11 12:34:06 +0000
+++ quickstart/utils.py 2014-03-14 18:27:29 +0000
@@ -30,6 +30,7 @@
30import logging30import logging
31import os31import os
32import pipes32import pipes
33import re
33import socket34import socket
34import subprocess35import subprocess
35import urllib236import urllib2
@@ -42,6 +43,18 @@
42from quickstart.models import charms43from quickstart.models import charms
4344
4445
46# Compile the regular expression used to parse bundle URLs.
47_bundle_expression = re.compile(r"""
48 # Bundle schema or bundle URL namespace on jujucharms.com.
49 ^(?:bundle:|{})
50 (?:~([-\w]+)/)? # Optional user name.
51 ([-\w]+)/ # Basket name.
52 (?:(\d+)/)? # Optional bundle revision number.
53 ([-\w]+) # Bundle name.
54 /?$ # Optional trailing slash.
55""".format(settings.JUJUCHARMS_BUNDLE_URL), re.VERBOSE)
56
57
45def add_apt_repository(repository):58def add_apt_repository(repository):
46 """Add the given APT repository to the current list of APT sources.59 """Add the given APT repository to the current list of APT sources.
4760
@@ -117,10 +130,14 @@
117130
118 Raise a ValueError if the given URL is not a valid bundle URL.131 Raise a ValueError if the given URL is not a valid bundle URL.
119 """132 """
120 bundle_id = bundle_url.split(':', 1)[1].rstrip('/')133 match = _bundle_expression.match(bundle_url)
121 if not bundle_id:134 if match is None:
122 msg = 'invalid bundle URL: {}'.format(bundle_url)135 msg = 'invalid bundle URL: {}'.format(bundle_url)
123 raise ValueError(msg.encode('utf-8'))136 raise ValueError(msg.encode('utf-8'))
137 user, basket, revision, name = match.groups()
138 user_part = '~charmers/' if user is None else '~{}/'.format(user)
139 revision_part = '' if revision is None else '{}/'.format(revision)
140 bundle_id = '{}{}/{}{}'.format(user_part, basket, revision_part, name)
124 return ('https://manage.jujucharms.com/bundle/{}/json'.format(bundle_id),141 return ('https://manage.jujucharms.com/bundle/{}/json'.format(bundle_id),
125 bundle_id)142 bundle_id)
126143

Subscribers

People subscribed via source and target branches