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

Subscribers

People subscribed via source and target branches