Merge lp:~bac/juju-quickstart/platform-settings-2 into lp:juju-quickstart
- platform-settings-2
- Merge into trunk
Proposed by
Brad Crittenden
Status: | Merged |
---|---|
Merged at revision: | 71 |
Proposed branch: | lp:~bac/juju-quickstart/platform-settings-2 |
Merge into: | lp:juju-quickstart |
Diff against target: |
963 lines (+230/-176) 9 files modified
quickstart/app.py (+11/-15) quickstart/manage.py (+41/-14) quickstart/platform_support.py (+31/-38) quickstart/settings.py (+16/-2) quickstart/tests/test_app.py (+46/-51) quickstart/tests/test_manage.py (+57/-16) quickstart/tests/test_platform_support.py (+23/-35) quickstart/tests/test_utils.py (+3/-3) quickstart/utils.py (+2/-2) |
To merge this branch: | bzr merge lp:~bac/juju-quickstart/platform-settings-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+221745@code.launchpad.net |
Commit message
Description of the change
Reorganize platform settings.
The first cut of the platform work was a bit unclean with respect to the
dividing lines between the quickstart app code and the parts that can be
re-used as a library. This branch moves things around to re-attain that
separation.
To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote : | # |
Revision history for this message
Brad Crittenden (bac) wrote : | # |
*** Submitted:
Reorganize platform settings.
The first cut of the platform work was a bit unclean with respect to the
dividing lines between the quickstart app code and the parts that can be
re-used as a library. This branch moves things around to re-attain that
separation.
R=redir
CC=
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'quickstart/app.py' |
2 | --- quickstart/app.py 2014-05-29 18:07:12 +0000 |
3 | +++ quickstart/app.py 2014-06-02 14:34:25 +0000 |
4 | @@ -58,7 +58,7 @@ |
5 | return b'juju-quickstart: error: {}'.format(self.message) |
6 | |
7 | |
8 | -def ensure_dependencies(distro_only): |
9 | +def ensure_dependencies(distro_only, platform, juju_command): |
10 | """Ensure that Juju and LXC are installed. |
11 | |
12 | If the "juju" command is not found in the PATH, then install and |
13 | @@ -77,22 +77,19 @@ |
14 | the Juju version. |
15 | """ |
16 | try: |
17 | - platform, installer = platform_support.get_platform_installer() |
18 | + installer = platform_support.get_juju_installer(platform) |
19 | except platform_support.UnsupportedOS as err: |
20 | raise ProgramExit(bytes(err)) |
21 | |
22 | - if installer is None: |
23 | - raise ProgramExit(b'No installer found for host platform.') |
24 | - |
25 | required_packages = [] |
26 | # Check if juju is installed. |
27 | try: |
28 | - juju_version = utils.get_juju_version() |
29 | + juju_version = utils.get_juju_version(juju_command) |
30 | except ValueError: |
31 | # Juju is not installed or configured properly. To ensure everything |
32 | # is set up correctly, also install packages required to run |
33 | # environments using the local provider. |
34 | - required_packages.extend(platform_support.JUJU_PACKAGES.get(platform)) |
35 | + required_packages.extend(settings.JUJU_PACKAGES.get(platform)) |
36 | juju_version = None |
37 | else: |
38 | if platform_support.supports_local(platform): |
39 | @@ -113,7 +110,7 @@ |
40 | if juju_version is None: |
41 | # Juju has been just installed, retrieve its version. |
42 | try: |
43 | - juju_version = utils.get_juju_version() |
44 | + juju_version = utils.get_juju_version(juju_command) |
45 | except ValueError as err: |
46 | raise ProgramExit(bytes(err)) |
47 | return juju_version |
48 | @@ -161,13 +158,12 @@ |
49 | b'\nIf you would like to create the keys yourself,\n' |
50 | b'please run this command, follow its instructions,\n' |
51 | b'and then re-run quickstart:\n' |
52 | - b' ssh-keygen -b 4096 -t rsa' |
53 | - ) |
54 | + b' ssh-keygen -b 4096 -t rsa') |
55 | except OSError as err: |
56 | raise ProgramExit(bytes(err)) |
57 | |
58 | |
59 | -def bootstrap(env_name, requires_sudo=False, debug=False): |
60 | +def bootstrap(env_name, juju_command, requires_sudo=False, debug=False): |
61 | """Bootstrap the Juju environment with the given name. |
62 | |
63 | Do not bootstrap the environment if already bootstrapped. |
64 | @@ -187,7 +183,7 @@ |
65 | Raise a ProgramExit if any error occurs in the bootstrap process. |
66 | """ |
67 | already_bootstrapped = False |
68 | - cmd = [settings.JUJU_CMD, 'bootstrap', '-e', env_name] |
69 | + cmd = [juju_command, 'bootstrap', '-e', env_name] |
70 | if requires_sudo: |
71 | cmd.insert(0, 'sudo') |
72 | if debug: |
73 | @@ -224,7 +220,7 @@ |
74 | timeout = time.time() + (60*10) |
75 | while time.time() < timeout: |
76 | retcode, output, error = utils.call( |
77 | - settings.JUJU_CMD, 'status', '-e', env_name, '--format', 'yaml') |
78 | + juju_command, 'status', '-e', env_name, '--format', 'yaml') |
79 | if retcode: |
80 | continue |
81 | # Ensure the state server is up and the agent is started. |
82 | @@ -266,7 +262,7 @@ |
83 | raise ValueError(msg.encode('utf-8')) |
84 | |
85 | |
86 | -def get_api_url(env_name): |
87 | +def get_api_url(env_name, juju_command): |
88 | """Return a Juju API URL for the given environment name. |
89 | |
90 | Use the Juju CLI in a subprocess in order to retrieve the API addresses. |
91 | @@ -274,7 +270,7 @@ |
92 | Raise a ProgramExit if any error occurs. |
93 | """ |
94 | retcode, output, error = utils.call( |
95 | - settings.JUJU_CMD, 'api-endpoints', '-e', env_name, '--format', 'json') |
96 | + juju_command, 'api-endpoints', '-e', env_name, '--format', 'json') |
97 | if retcode: |
98 | raise ProgramExit(error) |
99 | # Assuming there is always at least one API address, grab the first one |
100 | |
101 | === modified file 'quickstart/manage.py' |
102 | --- quickstart/manage.py 2014-05-30 12:26:45 +0000 |
103 | +++ quickstart/manage.py 2014-06-02 14:34:25 +0000 |
104 | @@ -33,6 +33,7 @@ |
105 | from quickstart import ( |
106 | app, |
107 | packaging, |
108 | + platform_support, |
109 | settings, |
110 | utils, |
111 | ) |
112 | @@ -54,7 +55,7 @@ |
113 | parser.exit() |
114 | |
115 | |
116 | -def _get_packaging_info(juju_source): |
117 | +def _get_packaging_info(juju_source, platform): |
118 | """Return packaging info based on the given juju source. |
119 | |
120 | The juju_source argument can be either "ppa" or "distro". |
121 | @@ -65,16 +66,23 @@ |
122 | - distro_only_help: the help text for the --distro-only flag; |
123 | - ppa_help: the help text for the --ppa flag. |
124 | """ |
125 | - distro_only_help = ('Do not use external sources when installing and ' |
126 | - 'setting up Juju') |
127 | - ppa_help = 'Use external sources when installing and setting up Juju' |
128 | - disable_help = '\n(enabled by default, use {} to disable)' |
129 | - if juju_source == 'distro': |
130 | + if platform == settings.LINUX_APT: |
131 | + distro_only_help = ('Do not use external sources when installing and ' |
132 | + 'setting up Juju') |
133 | + ppa_help = 'Use external sources when installing and setting up Juju' |
134 | + disable_help = '\n(enabled by default, use {} to disable)' |
135 | + if juju_source == 'distro': |
136 | + distro_only = True |
137 | + distro_only_help += disable_help.format('--ppa') |
138 | + else: |
139 | + distro_only = False |
140 | + ppa_help += disable_help.format('--distro-only') |
141 | + else: |
142 | + # If not LINUX_APT then distro_only and ppa make no sense. Prevent |
143 | + # the user from seeing them but allow reasonable values in options. |
144 | distro_only = True |
145 | - distro_only_help += disable_help.format('--ppa') |
146 | - else: |
147 | - distro_only = False |
148 | - ppa_help += disable_help.format('--distro-only') |
149 | + distro_only_help = argparse.SUPPRESS |
150 | + ppa_help = argparse.SUPPRESS |
151 | return distro_only, distro_only_help, ppa_help |
152 | |
153 | |
154 | @@ -329,6 +337,7 @@ |
155 | - env_type: the provider type of the selected Juju environment; |
156 | - interactive: whether to start the interactive session; |
157 | - open_browser: whether the GUI browser must be opened. |
158 | + - platform: The host platform. |
159 | |
160 | The following attributes will also be included in the namespace if a bundle |
161 | deployment is requested: |
162 | @@ -340,9 +349,17 @@ |
163 | |
164 | Exit with an error if the provided arguments are not valid. |
165 | """ |
166 | + |
167 | + # Determine the host platform. This needs to be done early as it affects |
168 | + # the options we present and allows an early failure. |
169 | + try: |
170 | + platform = platform_support.get_platform() |
171 | + except platform_support.UnsupportedOS as err: |
172 | + raise app.ProgramExit(bytes(err)) |
173 | + |
174 | default_env_name = envs.get_default_env_name() |
175 | default_distro_only, distro_only_help, ppa_help = _get_packaging_info( |
176 | - packaging.JUJU_SOURCE) |
177 | + packaging.JUJU_SOURCE, platform) |
178 | # Define the help message for the --environment option. |
179 | env_help = 'The name of the Juju environment to use' |
180 | if default_env_name is not None: |
181 | @@ -401,6 +418,7 @@ |
182 | parser.add_argument( |
183 | '--no-browser', action='store_false', dest='open_browser', |
184 | help='Avoid opening the browser to the GUI at the end of the\nprocess') |
185 | + |
186 | parser.add_argument( |
187 | '--distro-only', action='store_true', dest='distro_only', |
188 | default=default_distro_only, help=distro_only_help) |
189 | @@ -420,6 +438,10 @@ |
190 | nargs=0, help="Show program's description and exit") |
191 | # Parse the provided arguments. |
192 | options = parser.parse_args() |
193 | + |
194 | + # Add in the platform for convenience. |
195 | + options.platform = platform |
196 | + |
197 | # Convert the provided string arguments to unicode. |
198 | _convert_options_to_unicode(options) |
199 | # Validate and process the provided arguments. |
200 | @@ -440,8 +462,11 @@ |
201 | print('contents loaded for bundle {} (services: {})'.format( |
202 | options.bundle_name, len(options.bundle_services))) |
203 | |
204 | + juju_command = platform_support.get_juju_command(options.platform) |
205 | + |
206 | logging.debug('ensuring juju and dependencies are installed') |
207 | - juju_version = app.ensure_dependencies(options.distro_only) |
208 | + juju_version = app.ensure_dependencies( |
209 | + options.distro_only, options.platform, juju_command) |
210 | |
211 | logging.debug('ensuring SSH keys are available') |
212 | app.ensure_ssh_keys() |
213 | @@ -456,8 +481,10 @@ |
214 | print('sudo privileges will be required to bootstrap the environment') |
215 | # If the Juju version is less than 1.17.2 then use sudo for local envs. |
216 | requires_sudo = juju_version < (1, 17, 2) |
217 | + |
218 | already_bootstrapped, bootstrap_node_series = app.bootstrap( |
219 | - options.env_name, requires_sudo=requires_sudo, debug=options.debug) |
220 | + options.env_name, juju_command, |
221 | + requires_sudo=requires_sudo, debug=options.debug) |
222 | |
223 | # Retrieve the admin-secret for the current environment. |
224 | try: |
225 | @@ -472,7 +499,7 @@ |
226 | raise app.ProgramExit(msg) |
227 | |
228 | print('retrieving the Juju API address') |
229 | - api_url = app.get_api_url(options.env_name) |
230 | + api_url = app.get_api_url(options.env_name, juju_command) |
231 | print('connecting to {}'.format(api_url)) |
232 | env = app.connect(api_url, admin_secret) |
233 | |
234 | |
235 | === modified file 'quickstart/platform_support.py' |
236 | --- quickstart/platform_support.py 2014-05-29 20:49:20 +0000 |
237 | +++ quickstart/platform_support.py 2014-06-02 14:34:25 +0000 |
238 | @@ -36,12 +36,6 @@ |
239 | pass |
240 | |
241 | |
242 | -OSX = object() |
243 | -LINUX_APT = object() |
244 | -LINUX_RPM = object() |
245 | -WINDOWS = object() |
246 | - |
247 | - |
248 | def get_platform(): |
249 | """Return the platform of the host. |
250 | |
251 | @@ -49,14 +43,14 @@ |
252 | """ |
253 | system = platform.system() |
254 | if system == 'Darwin': |
255 | - return OSX |
256 | + return settings.OSX |
257 | elif system == 'Windows': |
258 | - return WINDOWS |
259 | + return settings.WINDOWS |
260 | elif system == 'Linux': |
261 | if os.path.isfile('/usr/bin/apt-get'): |
262 | - return LINUX_APT |
263 | + return settings.LINUX_APT |
264 | elif os.path.isfile('/usr/bin/rpm'): |
265 | - return LINUX_RPM |
266 | + return settings.LINUX_RPM |
267 | raise UnsupportedOS(b'{} without apt-get nor rpm'.format(system)) |
268 | elif system == '': |
269 | raise UnsupportedOS(b'Unable to determine the OS platform') |
270 | @@ -97,35 +91,34 @@ |
271 | |
272 | |
273 | INSTALLERS = { |
274 | - LINUX_APT: _installer_apt, |
275 | - OSX: _installer_osx, |
276 | -} |
277 | - |
278 | - |
279 | -JUJU_PACKAGES = { |
280 | - LINUX_APT: ('juju-core', 'juju-local'), |
281 | - OSX: ('juju', ), |
282 | -} |
283 | - |
284 | - |
285 | -ALTERNATE_JUJU_LOCATIONS = { |
286 | - OSX: '/usr/local/bin/juju', |
287 | -} |
288 | - |
289 | - |
290 | -def get_platform_installer(): |
291 | - """Return the platorm and the installer for the host. |
292 | - |
293 | - Raises UnsupportedOS if the OS is unknown. |
294 | - Returns platform and installer callable. The installer_callable is None |
295 | - if no installer can be found for the platform. |
296 | - """ |
297 | - platform = get_platform() |
298 | - if platform in ALTERNATE_JUJU_LOCATIONS: |
299 | - settings.JUJU_CMD = ALTERNATE_JUJU_LOCATIONS[platform] |
300 | - return platform, INSTALLERS.get(platform) |
301 | + settings.LINUX_APT: _installer_apt, |
302 | + settings.OSX: _installer_osx, |
303 | +} |
304 | + |
305 | + |
306 | +def get_juju_command(platform): |
307 | + """Return the path to the Juju command on the given platform. |
308 | + |
309 | + If the platform does not have a novel location, the default will be |
310 | + returned. |
311 | + """ |
312 | + return settings.JUJU_CMD_PATHS.get( |
313 | + platform, |
314 | + settings.JUJU_CMD_PATHS['default']) |
315 | + |
316 | + |
317 | +def get_juju_installer(platform): |
318 | + """Return the installer for the host platform. |
319 | + |
320 | + Returns installer callable. |
321 | + Raises UnsupportedOS if a platform we don't support is detected. |
322 | + """ |
323 | + installer = INSTALLERS.get(platform) |
324 | + if installer is None: |
325 | + raise UnsupportedOS(b'No installer found for host platform.') |
326 | + return installer |
327 | |
328 | |
329 | def supports_local(platform): |
330 | """Return True if the platform supports local (LXC) deploys.""" |
331 | - return platform in (LINUX_APT, LINUX_RPM) |
332 | + return platform in (settings.LINUX_APT, settings.LINUX_RPM) |
333 | |
334 | === modified file 'quickstart/settings.py' |
335 | --- quickstart/settings.py 2014-04-23 15:53:32 +0000 |
336 | +++ quickstart/settings.py 2014-06-02 14:34:25 +0000 |
337 | @@ -21,6 +21,11 @@ |
338 | import collections |
339 | import os |
340 | |
341 | +# Platforms. |
342 | +OSX = object() |
343 | +LINUX_APT = object() |
344 | +LINUX_RPM = object() |
345 | +WINDOWS = object() |
346 | |
347 | # The base charmworld API URL containing information about charms. |
348 | # This URL must be formatted with a series and a charm name. |
349 | @@ -41,8 +46,17 @@ |
350 | # The URL namespace for bundles in jujucharms.com. |
351 | JUJUCHARMS_BUNDLE_URL = 'https://jujucharms.com/bundle/' |
352 | |
353 | -# The path to the Juju command. |
354 | -JUJU_CMD = '/usr/bin/juju' |
355 | +# The path to the Juju command, based on platform. |
356 | +JUJU_CMD_PATHS = { |
357 | + 'default': '/usr/bin/juju', |
358 | + OSX: '/usr/local/bin/juju', |
359 | +} |
360 | + |
361 | +# Juju packages to install per platform. |
362 | +JUJU_PACKAGES = { |
363 | + LINUX_APT: ('juju-core', 'juju-local'), |
364 | + OSX: ('juju', ), |
365 | +} |
366 | |
367 | # The possible values for the environments.yaml default-series field. |
368 | JUJU_DEFAULT_SERIES = ('precise', 'quantal', 'raring', 'saucy', 'trusty') |
369 | |
370 | === modified file 'quickstart/tests/test_app.py' |
371 | --- quickstart/tests/test_app.py 2014-05-29 18:07:12 +0000 |
372 | +++ quickstart/tests/test_app.py 2014-06-02 14:34:25 +0000 |
373 | @@ -68,10 +68,11 @@ |
374 | add_repository = '/usr/bin/add-apt-repository' |
375 | apt_get = '/usr/bin/apt-get' |
376 | brew = '/usr/local/bin/brew' |
377 | + juju_command = settings.JUJU_CMD_PATHS['default'] |
378 | |
379 | def call_ensure_dependencies( |
380 | self, call_effects, distro_only=False, |
381 | - platform=platform_support.LINUX_APT, |
382 | + platform=settings.LINUX_APT, |
383 | platform_installer=platform_support._installer_apt): |
384 | """Execute the quickstart.app.ensure_dependencies call. |
385 | |
386 | @@ -81,10 +82,10 @@ |
387 | Return the mock call object and the ensure_dependencies return value. |
388 | """ |
389 | with self.patch_multiple_calls(call_effects) as mock_call: |
390 | - with mock.patch( |
391 | - 'quickstart.app.platform_support.get_platform_installer', |
392 | - side_effect=[(platform, platform_installer)]): |
393 | - juju_version = app.ensure_dependencies(distro_only) |
394 | + path = 'quickstart.app.platform_support.get_juju_installer' |
395 | + with mock.patch(path, side_effect=[platform_installer]): |
396 | + juju_version = app.ensure_dependencies( |
397 | + distro_only, platform, self.juju_command) |
398 | return mock_call, juju_version |
399 | |
400 | def test_success_install_apt(self, mock_print): |
401 | @@ -101,7 +102,7 @@ |
402 | mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
403 | self.assertEqual(len(side_effects), mock_call.call_count) |
404 | mock_call.assert_has_calls([ |
405 | - mock.call(settings.JUJU_CMD, 'version'), |
406 | + mock.call(self.juju_command, 'version'), |
407 | mock.call('lsb_release', '-cs'), |
408 | mock.call('sudo', self.apt_get, 'install', '-y', |
409 | 'software-properties-common'), |
410 | @@ -109,7 +110,7 @@ |
411 | mock.call('sudo', self.apt_get, 'update'), |
412 | mock.call('sudo', self.apt_get, 'install', '-y', |
413 | 'juju-core', 'juju-local'), |
414 | - mock.call(settings.JUJU_CMD, 'version'), |
415 | + mock.call(self.juju_command, 'version'), |
416 | ]) |
417 | mock_print.assert_has_calls([ |
418 | mock.call('adding the ppa:juju/stable PPA repository'), |
419 | @@ -128,37 +129,27 @@ |
420 | (0, '1.18.0', ''), # Retrieve the version again. |
421 | ) |
422 | mock_call, juju_version = self.call_ensure_dependencies( |
423 | - side_effects, platform=platform_support.OSX, |
424 | + side_effects, platform=settings.OSX, |
425 | platform_installer=platform_support._installer_osx) |
426 | self.assertEqual(len(side_effects), mock_call.call_count) |
427 | mock_call.assert_has_calls([ |
428 | - mock.call(settings.JUJU_CMD, 'version'), |
429 | + mock.call(self.juju_command, 'version'), |
430 | mock.call(self.brew, 'install', 'juju'), |
431 | - mock.call(settings.JUJU_CMD, 'version'), |
432 | + mock.call(self.juju_command, 'version'), |
433 | ]) |
434 | mock_print.assert_has_calls([ |
435 | mock.call('Installing the following packages: juju\n'), |
436 | ]) |
437 | self.assertEqual((1, 18, 0), juju_version) |
438 | |
439 | - def test_unsupported_os(self, mock_print): |
440 | - # get_platform_installer raises UnsupportedOS, causing ProgramExit. |
441 | - path = 'quickstart.app.platform_support.get_platform_installer' |
442 | - err = platform_support.UnsupportedOS('Unknown TRS-80') |
443 | - with mock.patch(path, side_effect=err): |
444 | - with self.assertRaises(app.ProgramExit) as ctx: |
445 | - app.ensure_dependencies(False) |
446 | - expected = b'juju-quickstart: error: Unknown TRS-80' |
447 | - self.assertEqual(expected, bytes(ctx.exception)) |
448 | - |
449 | def test_no_installer(self, mock_print): |
450 | # If no installer is found a ProgramExit is raised. |
451 | - path = 'quickstart.app.platform_support.get_platform_installer' |
452 | - with mock.patch(path, side_effect=[('unknown', None)]): |
453 | + path = 'quickstart.app.platform_support.get_juju_installer' |
454 | + err = platform_support.UnsupportedOS('unknown') |
455 | + with mock.patch(path, side_effect=err): |
456 | with self.assertRaises(app.ProgramExit) as ctx: |
457 | - app.ensure_dependencies(False) |
458 | - expected = (b'juju-quickstart: error: ' |
459 | - 'No installer found for host platform.') |
460 | + app.ensure_dependencies(False, None, None) |
461 | + expected = (b'juju-quickstart: error: unknown') |
462 | self.assertEqual(expected, bytes(ctx.exception)) |
463 | |
464 | def test_distro_only_install(self, mock_print): |
465 | @@ -172,10 +163,10 @@ |
466 | side_effects, distro_only=True) |
467 | self.assertEqual(len(side_effects), mock_call.call_count) |
468 | mock_call.assert_has_calls([ |
469 | - mock.call(settings.JUJU_CMD, 'version'), |
470 | + mock.call(self.juju_command, 'version'), |
471 | mock.call('sudo', self.apt_get, 'install', '-y', |
472 | 'juju-core', 'juju-local'), |
473 | - mock.call(settings.JUJU_CMD, 'version'), |
474 | + mock.call(self.juju_command, 'version'), |
475 | ]) |
476 | mock_print.assert_called_once_with( |
477 | 'sudo privileges will be used for the installation of \n' |
478 | @@ -211,7 +202,7 @@ |
479 | mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
480 | self.assertEqual(len(side_effects), mock_call.call_count) |
481 | mock_call.assert_has_calls([ |
482 | - mock.call(settings.JUJU_CMD, 'version'), |
483 | + mock.call(self.juju_command, 'version'), |
484 | mock.call('/usr/bin/lxc-ls'), |
485 | mock.call('lsb_release', '-cs'), |
486 | mock.call('sudo', self.apt_get, 'install', '-y', |
487 | @@ -241,7 +232,7 @@ |
488 | side_effects, distro_only=True) |
489 | self.assertEqual(len(side_effects), mock_call.call_count) |
490 | mock_call.assert_has_calls([ |
491 | - mock.call(settings.JUJU_CMD, 'version'), |
492 | + mock.call(self.juju_command, 'version'), |
493 | mock.call('/usr/bin/lxc-ls'), |
494 | mock.call('sudo', self.apt_get, 'install', '-y', 'juju-local'), |
495 | ]) |
496 | @@ -286,7 +277,7 @@ |
497 | with self.assert_program_exit('install error'): |
498 | mock_call = self.call_ensure_dependencies( |
499 | side_effects, |
500 | - platform=platform_support.OSX, |
501 | + platform=settings.OSX, |
502 | platform_installer=platform_support._installer_osx)[0] |
503 | self.assertEqual(2, mock_call.call_count) |
504 | |
505 | @@ -447,6 +438,7 @@ |
506 | |
507 | env_name = 'my-juju-env' |
508 | status_message = 'retrieving the environment status' |
509 | + juju_command = settings.JUJU_CMD_PATHS['default'] |
510 | |
511 | def make_status_output(self, agent_state, series='hoary'): |
512 | """Create and return a YAML status output.""" |
513 | @@ -458,7 +450,7 @@ |
514 | def make_status_calls(self, number): |
515 | """Return a list containing the given number of status calls.""" |
516 | call = mock.call( |
517 | - settings.JUJU_CMD, 'status', '-e', self.env_name, |
518 | + self.juju_command, 'status', '-e', self.env_name, |
519 | '--format', 'yaml') |
520 | return [call for _ in range(number)] |
521 | |
522 | @@ -475,19 +467,20 @@ |
523 | Receive the list of side effects the mock status call will return. |
524 | """ |
525 | with self.patch_multiple_calls(side_effects) as mock_call: |
526 | - app.bootstrap(self.env_name) |
527 | + app.bootstrap(self.env_name, self.juju_command) |
528 | mock_call.assert_has_calls([ |
529 | - mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
530 | + mock.call(self.juju_command, 'bootstrap', '-e', self.env_name), |
531 | ] + self.make_status_calls(5)) |
532 | |
533 | def test_success(self, mock_print): |
534 | # The environment is successfully bootstrapped. |
535 | with self.patch_multiple_calls(self.make_side_effects()) as mock_call: |
536 | - already_bootstrapped, series = app.bootstrap(self.env_name) |
537 | + already_bootstrapped, series = app.bootstrap( |
538 | + self.env_name, self.juju_command) |
539 | self.assertFalse(already_bootstrapped) |
540 | self.assertEqual(series, 'hoary') |
541 | mock_call.assert_has_calls([ |
542 | - mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
543 | + mock.call(self.juju_command, 'bootstrap', '-e', self.env_name), |
544 | ] + self.make_status_calls(1)) |
545 | mock_print.assert_called_once_with(self.status_message) |
546 | |
547 | @@ -495,12 +488,12 @@ |
548 | # The environment is bootstrapped with sudo using the local provider. |
549 | with self.patch_multiple_calls(self.make_side_effects()) as mock_call: |
550 | already_bootstrapped, series = app.bootstrap( |
551 | - self.env_name, requires_sudo=True) |
552 | + self.env_name, self.juju_command, requires_sudo=True) |
553 | self.assertFalse(already_bootstrapped) |
554 | self.assertEqual(series, 'hoary') |
555 | mock_call.assert_has_calls([ |
556 | mock.call( |
557 | - 'sudo', settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
558 | + 'sudo', self.juju_command, 'bootstrap', '-e', self.env_name), |
559 | ] + self.make_status_calls(1)) |
560 | mock_print.assert_called_once_with(self.status_message) |
561 | |
562 | @@ -508,12 +501,12 @@ |
563 | # The environment is successfully bootstrapped in debug mode. |
564 | with self.patch_multiple_calls(self.make_side_effects()) as mock_call: |
565 | already_bootstrapped, series = app.bootstrap( |
566 | - self.env_name, debug=True) |
567 | + self.env_name, self.juju_command, debug=True) |
568 | self.assertFalse(already_bootstrapped) |
569 | self.assertEqual(series, 'hoary') |
570 | mock_call.assert_has_calls([ |
571 | mock.call( |
572 | - settings.JUJU_CMD, 'bootstrap', '-e', self.env_name, |
573 | + self.juju_command, 'bootstrap', '-e', self.env_name, |
574 | '--debug'), |
575 | ] + self.make_status_calls(1)) |
576 | |
577 | @@ -525,11 +518,12 @@ |
578 | (0, self.make_status_output('started', 'precise'), ''), |
579 | ] |
580 | with self.patch_multiple_calls(side_effects) as mock_call: |
581 | - already_bootstrapped, series = app.bootstrap(self.env_name) |
582 | + already_bootstrapped, series = app.bootstrap( |
583 | + self.env_name, self.juju_command) |
584 | self.assertTrue(already_bootstrapped) |
585 | self.assertEqual(series, 'precise') |
586 | mock_call.assert_has_calls([ |
587 | - mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
588 | + mock.call(self.juju_command, 'bootstrap', '-e', self.env_name), |
589 | ] + self.make_status_calls(1)) |
590 | existing_message = 'reusing the already bootstrapped {} environment' |
591 | mock_print.assert_has_calls([ |
592 | @@ -541,9 +535,9 @@ |
593 | # A ProgramExit is raised if an error occurs while bootstrapping. |
594 | with self.patch_call(retcode=1, error='bad wolf') as mock_call: |
595 | with self.assert_program_exit('bad wolf'): |
596 | - app.bootstrap(self.env_name) |
597 | + app.bootstrap(self.env_name, self.juju_command) |
598 | mock_call.assert_called_once_with( |
599 | - settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
600 | + self.juju_command, 'bootstrap', '-e', self.env_name), |
601 | |
602 | def test_status_retry_error(self, mock_print): |
603 | # Before raising a ProgramExit, the functions tries to call |
604 | @@ -602,9 +596,9 @@ |
605 | expected = 'state server failure:\n{}'.format(status_output) |
606 | with self.patch_multiple_calls(side_effects) as mock_call: |
607 | with self.assert_program_exit(expected): |
608 | - app.bootstrap(self.env_name) |
609 | + app.bootstrap(self.env_name, self.juju_command) |
610 | mock_call.assert_has_calls([ |
611 | - mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
612 | + mock.call(self.juju_command, 'bootstrap', '-e', self.env_name), |
613 | ] + self.make_status_calls(1)) |
614 | |
615 | def test_status_failure(self, mock_print): |
616 | @@ -628,9 +622,9 @@ |
617 | # the third for the second status check, the fourth should fail. |
618 | with mock.patch('time.time', mock_time): |
619 | with self.assert_program_exit(expected): |
620 | - app.bootstrap(self.env_name) |
621 | + app.bootstrap(self.env_name, self.juju_command) |
622 | mock_call.assert_has_calls([ |
623 | - mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
624 | + mock.call(self.juju_command, 'bootstrap', '-e', self.env_name), |
625 | ] + self.make_status_calls(2)) |
626 | |
627 | |
628 | @@ -660,24 +654,25 @@ |
629 | helpers.CallTestsMixin, ProgramExitTestsMixin, unittest.TestCase): |
630 | |
631 | env_name = 'ec2' |
632 | + juju_command = settings.JUJU_CMD_PATHS['default'] |
633 | |
634 | def test_success(self): |
635 | # The API URL is correctly returned. |
636 | api_addresses = json.dumps(['api.example.com:17070', 'not-today']) |
637 | with self.patch_call(retcode=0, output=api_addresses) as mock_call: |
638 | - api_url = app.get_api_url(self.env_name) |
639 | + api_url = app.get_api_url(self.env_name, self.juju_command) |
640 | self.assertEqual('wss://api.example.com:17070', api_url) |
641 | mock_call.assert_called_once_with( |
642 | - settings.JUJU_CMD, 'api-endpoints', '-e', self.env_name, |
643 | + self.juju_command, 'api-endpoints', '-e', self.env_name, |
644 | '--format', 'json') |
645 | |
646 | def test_failure(self): |
647 | # A ProgramExit is raised if an error occurs retrieving the API URL. |
648 | with self.patch_call(retcode=1, error='bad wolf') as mock_call: |
649 | with self.assert_program_exit('bad wolf'): |
650 | - app.get_api_url(self.env_name) |
651 | + app.get_api_url(self.env_name, self.juju_command) |
652 | mock_call.assert_called_once_with( |
653 | - settings.JUJU_CMD, 'api-endpoints', '-e', self.env_name, |
654 | + self.juju_command, 'api-endpoints', '-e', self.env_name, |
655 | '--format', 'json') |
656 | |
657 | |
658 | |
659 | === modified file 'quickstart/tests/test_manage.py' |
660 | --- quickstart/tests/test_manage.py 2014-05-23 13:26:56 +0000 |
661 | +++ quickstart/tests/test_manage.py 2014-06-02 14:34:25 +0000 |
662 | @@ -38,7 +38,10 @@ |
663 | from quickstart.cli import views |
664 | from quickstart.models import envs |
665 | from quickstart.tests import helpers |
666 | -from quickstart import app |
667 | +from quickstart import ( |
668 | + app, |
669 | + platform_support, |
670 | +) |
671 | |
672 | |
673 | class TestDescriptionAction(unittest.TestCase): |
674 | @@ -63,24 +66,34 @@ |
675 | distro_only_disable = '(enabled by default, use --ppa to disable)' |
676 | ppa_disable = '(enabled by default, use --distro-only to disable)' |
677 | |
678 | - def test_ppa_source(self): |
679 | + def test_ppa_source_apt(self): |
680 | # The returned distro_only flag is set to False and the help texts are |
681 | # formatted accordingly when the passed Juju source is "ppa". |
682 | distro_only, distro_only_help, ppa_help = manage._get_packaging_info( |
683 | - 'ppa') |
684 | + 'ppa', platform=settings.LINUX_APT) |
685 | self.assertFalse(distro_only) |
686 | self.assertNotIn(self.distro_only_disable, distro_only_help) |
687 | self.assertIn(self.ppa_disable, ppa_help) |
688 | |
689 | - def test_distro_source(self): |
690 | + def test_distro_source_apt(self): |
691 | # The returned distro_only flag is set to True and the help texts are |
692 | # formatted accordingly when the passed Juju source is "distro". |
693 | distro_only, distro_only_help, ppa_help = manage._get_packaging_info( |
694 | - 'distro') |
695 | + 'distro', platform=settings.LINUX_APT) |
696 | self.assertTrue(distro_only) |
697 | self.assertIn(self.distro_only_disable, distro_only_help) |
698 | self.assertNotIn(self.ppa_disable, ppa_help) |
699 | |
700 | + def test_not_apt(self): |
701 | + # If the platform is not LINUX_APT then distro_only should be False |
702 | + # and the help is suppressed. |
703 | + for platform in (settings.LINUX_RPM, settings.WINDOWS, settings.OSX): |
704 | + distro_only, dist_only_help, ppa_help = manage._get_packaging_info( |
705 | + 'dontcare', platform=platform) |
706 | + self.assertTrue(distro_only) |
707 | + self.assertEqual(argparse.SUPPRESS, dist_only_help) |
708 | + self.assertEqual(argparse.SUPPRESS, ppa_help) |
709 | + |
710 | |
711 | class TestValidateBundle( |
712 | helpers.BundleFileTestsMixin, helpers.UrlReadTestsMixin, |
713 | @@ -610,6 +623,16 @@ |
714 | if exit_called: |
715 | mock_exit.assert_called_once_with(0) |
716 | |
717 | + def test_unsupported_os(self): |
718 | + # get_platform raises UnsupportedOS, causing early ProgramExit. |
719 | + path = 'quickstart.manage.platform_support.get_platform' |
720 | + err = platform_support.UnsupportedOS('Unknown TRS-80') |
721 | + with mock.patch(path, side_effect=err): |
722 | + with self.assertRaises(app.ProgramExit) as ctx: |
723 | + manage.setup() |
724 | + expected = b'juju-quickstart: error: Unknown TRS-80' |
725 | + self.assertEqual(expected, bytes(ctx.exception)) |
726 | + |
727 | def test_help(self): |
728 | # The program help message is properly formatted. |
729 | with mock.patch('sys.stdout') as mock_stdout: |
730 | @@ -689,10 +712,12 @@ |
731 | @mock.patch('__builtin__.print', mock.Mock()) |
732 | class TestRun(unittest.TestCase): |
733 | |
734 | + juju_command = '/sbin/juju' |
735 | + |
736 | def make_options(self, **kwargs): |
737 | """Set up the options to be passed to the run function.""" |
738 | options = { |
739 | - 'admin_secret': 'Secret!', |
740 | + 'admin_secret': 'Secretz!', |
741 | 'bundle': None, |
742 | 'bundle_id': None, |
743 | 'charm_url': None, |
744 | @@ -734,14 +759,19 @@ |
745 | # Make mock_app.create_auth_token return a fake authentication token. |
746 | mock_app.create_auth_token.return_value = 'AUTHTOKEN' |
747 | options = self.make_options() |
748 | - manage.run(options) |
749 | + with mock.patch('quickstart.manage.platform_support.get_juju_command', |
750 | + side_effect=[self.juju_command]): |
751 | + manage.run(options) |
752 | mock_app.ensure_dependencies.assert_called() |
753 | mock_app.ensure_ssh_keys.assert_called() |
754 | mock_app.bootstrap.assert_called_once_with( |
755 | - options.env_name, requires_sudo=False, debug=options.debug) |
756 | - mock_app.get_api_url.assert_called_once_with(options.env_name) |
757 | + options.env_name, self.juju_command, requires_sudo=False, |
758 | + debug=options.debug) |
759 | + mock_app.get_api_url.assert_called_once_with( |
760 | + options.env_name, self.juju_command) |
761 | mock_app.connect.assert_has_calls([ |
762 | - mock.call(mock_app.get_api_url(), options.admin_secret), |
763 | + mock.call( |
764 | + mock_app.get_api_url(), options.admin_secret), |
765 | mock.call().close(), |
766 | mock.call('wss://1.2.3.4:443/ws', options.admin_secret), |
767 | mock.call().close(), |
768 | @@ -811,9 +841,13 @@ |
769 | 'cs:precise/juju-gui-42', '0', None, None) |
770 | for version in versions: |
771 | mock_app.ensure_dependencies.return_value = version |
772 | - manage.run(options) |
773 | + with mock.patch( |
774 | + 'quickstart.manage.platform_support.get_juju_command', |
775 | + side_effect=[self.juju_command]): |
776 | + manage.run(options) |
777 | mock_app.bootstrap.assert_called_once_with( |
778 | - options.env_name, requires_sudo=False, debug=options.debug) |
779 | + options.env_name, self.juju_command, requires_sudo=False, |
780 | + debug=options.debug) |
781 | mock_app.bootstrap.reset_mock() |
782 | |
783 | def test_local_provider_requiring_sudo(self, mock_app, mock_open): |
784 | @@ -831,9 +865,13 @@ |
785 | 'cs:trusty/juju-gui-42', '0', None, None) |
786 | for version in versions: |
787 | mock_app.ensure_dependencies.return_value = version |
788 | - manage.run(options) |
789 | + with mock.patch( |
790 | + 'quickstart.manage.platform_support.get_juju_command', |
791 | + side_effect=[self.juju_command]): |
792 | + manage.run(options) |
793 | mock_app.bootstrap.assert_called_once_with( |
794 | - options.env_name, requires_sudo=True, debug=options.debug) |
795 | + options.env_name, self.juju_command, requires_sudo=True, |
796 | + debug=options.debug) |
797 | mock_app.bootstrap.reset_mock() |
798 | |
799 | def test_no_local_no_sudo(self, mock_app, mock_open): |
800 | @@ -847,9 +885,12 @@ |
801 | # where to deploy the charm, the service and unit data. |
802 | mock_app.check_environment.return_value = ( |
803 | 'cs:precise/juju-gui-42', '0', None, None) |
804 | - manage.run(options) |
805 | + with mock.patch('quickstart.manage.platform_support.get_juju_command', |
806 | + side_effect=[self.juju_command]): |
807 | + manage.run(options) |
808 | mock_app.bootstrap.assert_called_once_with( |
809 | - options.env_name, requires_sudo=False, debug=options.debug) |
810 | + options.env_name, self.juju_command, |
811 | + requires_sudo=False, debug=options.debug) |
812 | |
813 | def test_no_browser(self, mock_app, mock_open): |
814 | # It is possible to avoid opening the GUI in the browser. |
815 | |
816 | === modified file 'quickstart/tests/test_platform_support.py' |
817 | --- quickstart/tests/test_platform_support.py 2014-05-29 20:49:20 +0000 |
818 | +++ quickstart/tests/test_platform_support.py 2014-06-02 14:34:25 +0000 |
819 | @@ -44,23 +44,23 @@ |
820 | with self.patch_platform_system('Linux'): |
821 | with self.patch_isfile([True]): |
822 | result = platform_support.get_platform() |
823 | - self.assertEqual(platform_support.LINUX_APT, result) |
824 | + self.assertEqual(settings.LINUX_APT, result) |
825 | |
826 | def test_linux_rpm(self): |
827 | with self.patch_platform_system('Linux'): |
828 | with self.patch_isfile([False, True]): |
829 | result = platform_support.get_platform() |
830 | - self.assertEqual(platform_support.LINUX_RPM, result) |
831 | + self.assertEqual(settings.LINUX_RPM, result) |
832 | |
833 | def test_osx(self): |
834 | with self.patch_platform_system('Darwin'): |
835 | result = platform_support.get_platform() |
836 | - self.assertEqual(platform_support.OSX, result) |
837 | + self.assertEqual(settings.OSX, result) |
838 | |
839 | def test_windows(self): |
840 | with self.patch_platform_system('Windows'): |
841 | result = platform_support.get_platform() |
842 | - self.assertEqual(platform_support.WINDOWS, result) |
843 | + self.assertEqual(settings.WINDOWS, result) |
844 | |
845 | def test_unsupported_raises_exception(self): |
846 | with self.patch_platform_system('CP/M'): |
847 | @@ -91,50 +91,38 @@ |
848 | |
849 | def test_support_local(self): |
850 | expected = { |
851 | - platform_support.LINUX_APT: True, |
852 | - platform_support.LINUX_RPM: True, |
853 | - platform_support.OSX: False, |
854 | - platform_support.WINDOWS: False, |
855 | + settings.LINUX_APT: True, |
856 | + settings.LINUX_RPM: True, |
857 | + settings.OSX: False, |
858 | + settings.WINDOWS: False, |
859 | object(): False, |
860 | } |
861 | for key, value in expected.items(): |
862 | self.assertEqual(value, platform_support.supports_local(key)) |
863 | |
864 | |
865 | -class TestGetPlatformInstaller(unittest.TestCase): |
866 | +class TestGetJujuInstaller(unittest.TestCase): |
867 | |
868 | def test_linux_apt(self): |
869 | - expected = platform_support.LINUX_APT |
870 | - with mock.patch('quickstart.platform_support.get_platform', |
871 | - side_effect=[expected]): |
872 | - platform, installer = platform_support.get_platform_installer() |
873 | - self.assertEqual(expected, platform) |
874 | + platform = settings.LINUX_APT |
875 | + installer = platform_support.get_juju_installer(platform) |
876 | self.assertEqual(platform_support._installer_apt, installer) |
877 | |
878 | def test_osx(self): |
879 | - expected = platform_support.OSX |
880 | - # The JUJU_CMD should be the default location. |
881 | - self.assertEqual('/usr/bin/juju', settings.JUJU_CMD) |
882 | - with mock.patch('quickstart.platform_support.get_platform', |
883 | - side_effect=[expected]): |
884 | - platform, installer = platform_support.get_platform_installer() |
885 | - self.assertEqual(expected, platform) |
886 | + platform = settings.OSX |
887 | + installer = platform_support.get_juju_installer(platform) |
888 | self.assertEqual(platform_support._installer_osx, installer) |
889 | - # The JUJU_CMD should be changed as a side-effect. |
890 | - self.assertEqual('/usr/local/bin/juju', settings.JUJU_CMD) |
891 | |
892 | def test_linux_rpm(self): |
893 | - expected = platform_support.LINUX_RPM |
894 | - with mock.patch('quickstart.platform_support.get_platform', |
895 | - side_effect=[expected]): |
896 | - platform, installer = platform_support.get_platform_installer() |
897 | - self.assertEqual(expected, platform) |
898 | - self.assertIsNone(installer) |
899 | + platform = settings.LINUX_RPM |
900 | + with self.assertRaises(platform_support.UnsupportedOS) as ctx: |
901 | + platform_support.get_juju_installer(platform) |
902 | + self.assertEqual( |
903 | + 'No installer found for host platform.', ctx.exception.message) |
904 | |
905 | def test_windows(self): |
906 | - expected = platform_support.WINDOWS |
907 | - with mock.patch('quickstart.platform_support.get_platform', |
908 | - side_effect=[expected]): |
909 | - platform, installer = platform_support.get_platform_installer() |
910 | - self.assertEqual(expected, platform) |
911 | - self.assertIsNone(installer) |
912 | + platform = settings.WINDOWS |
913 | + with self.assertRaises(platform_support.UnsupportedOS) as ctx: |
914 | + platform_support.get_juju_installer(platform) |
915 | + self.assertEqual( |
916 | + 'No installer found for host platform.', ctx.exception.message) |
917 | |
918 | === modified file 'quickstart/tests/test_utils.py' |
919 | --- quickstart/tests/test_utils.py 2014-05-30 09:23:00 +0000 |
920 | +++ quickstart/tests/test_utils.py 2014-06-02 14:34:25 +0000 |
921 | @@ -759,17 +759,17 @@ |
922 | def test_return_deconstructed_version(self): |
923 | # Should return a deconstructed juju version. |
924 | with self.patch_call(0, '1.17.1-precise-amd64\n', ''): |
925 | - version = utils.get_juju_version() |
926 | + version = utils.get_juju_version('juju') |
927 | self.assertEqual((1, 17, 1), version) |
928 | |
929 | def test_juju_version_error(self): |
930 | # A ValueError is raised if "juju version" exits with an error. |
931 | with self.patch_call(1, 'foo', 'bad wolf'): |
932 | with self.assert_value_error('bad wolf'): |
933 | - utils.get_juju_version() |
934 | + utils.get_juju_version('juju') |
935 | |
936 | def test_invalid_version_string(self): |
937 | # A ValueError is raised if "juju version" outputs an invalid version. |
938 | with self.patch_call(0, '1.17-precise-amd64', ''): |
939 | with self.assert_value_error('invalid version string: 1.17'): |
940 | - utils.get_juju_version() |
941 | + utils.get_juju_version('juju') |
942 | |
943 | === modified file 'quickstart/utils.py' |
944 | --- quickstart/utils.py 2014-05-29 18:07:12 +0000 |
945 | +++ quickstart/utils.py 2014-06-02 14:34:25 +0000 |
946 | @@ -338,7 +338,7 @@ |
947 | return parse_status_output(output, ['machines', '0', 'series']) |
948 | |
949 | |
950 | -def get_juju_version(): |
951 | +def get_juju_version(juju_command): |
952 | """Return the current juju-core version. |
953 | |
954 | Return a (major:int, minor:int, patch:int) tuple, including major, minor |
955 | @@ -347,7 +347,7 @@ |
956 | Raise a ValueError if the "juju version" call exits with an error |
957 | or the returned version is not well formed. |
958 | """ |
959 | - retcode, output, error = call(settings.JUJU_CMD, 'version') |
960 | + retcode, output, error = call(juju_command, 'version') |
961 | if retcode: |
962 | raise ValueError(error.encode('utf-8')) |
963 | version_string = output.split('-')[0] |
Reviewers: mp+221745_ code.launchpad. net,
Message:
Please take a look.
Description:
Reorganize platform settings.
The first cut of the platform work was a bit unclean with respect to the
dividing lines between the quickstart app code and the parts that can be
re-used as a library. This branch moves things around to re-attain that
separation.
Francesco's original concerns are raised in the previous code review at:
https:/ /codereview. appspot. com/102870043
https:/ /code.launchpad .net/~bac/ juju-quickstart /platform- settings- 2/+merge/ 221745
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/101980050/
Affected files (+232, -176 lines): manage. py platform_ support. py settings. py tests/test_ app.py tests/test_ manage. py tests/test_ platform_ support. py tests/test_ utils.py
A [revision details]
M quickstart/app.py
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/utils.py