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