Merge lp:~bac/juju-quickstart/auth into lp:juju-quickstart

Proposed by Brad Crittenden
Status: Merged
Merged at revision: 50
Proposed branch: lp:~bac/juju-quickstart/auth
Merge into: lp:juju-quickstart
Diff against target: 501 lines (+224/-35)
7 files modified
quickstart/app.py (+23/-0)
quickstart/manage.py (+20/-5)
quickstart/models/envs.py (+39/-17)
quickstart/tests/cli/test_views.py (+2/-2)
quickstart/tests/models/test_envs.py (+68/-9)
quickstart/tests/test_app.py (+20/-0)
quickstart/tests/test_manage.py (+52/-2)
To merge this branch: bzr merge lp:~bac/juju-quickstart/auth
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+203631@code.launchpad.net

Description of the change

Get admin_secret from juju-generated jenv file.

As of Juju v 1.17 the admin-secret is stored in a generated file, located in
$JUJU_HOME/environments/<environment>.jenv.

If an admin-secret is provided in environments.yaml, it is used and also
stored in the jenv file. If none is given, then juju fabricates one and
stores it in the jenv file.

The admin-secret *field* is marked as optional now. For the case where a
local environment is used, Quickstart previously would generate an
admin-secret. That behavior is maintained by forcing 'admin-secret' to be in
the missing_fields list. This approach ensures backwards compatibility.

Note that the Juju-generated admin-secret is not available until after
bootstrapping the environment. Due to this behavior, when using juju v1.16
and not admin-secret is configured in the environments.yaml file, the failure
will not be reported until after bootstrapping. This approach seemed better
than introducing hard version checking.

https://codereview.appspot.com/57900043/

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Reviewers: mp+203631_code.launchpad.net,

Message:
Please take a look.

Description:
Get admin_secret from juju-generated jenv file.

As of Juju v 1.17 the admin-secret is stored in a generated file,
located in
$JUJU_HOME/environments/<environment>.jenv.

If an admin-secret is provided in environments.yaml, it is used and also
stored in the jenv file. If none is given, then juju fabricates one and
stores it in the jenv file.

The admin-secret *field* is marked as optional now. For the case where
a
local environment is used, Quickstart previously would generate an
admin-secret. That behavior is maintained by forcing 'admin-secret' to
be in
the missing_fields list. This approach ensures backwards compatibility.

Note that the Juju-generated admin-secret is not available until after
bootstrapping the environment. Due to this behavior, when using juju
v1.16
and not admin-secret is configured in the environments.yaml file, the
failure
will not be reported until after bootstrapping. This approach seemed
better
than introducing hard version checking.

https://code.launchpad.net/~bac/juju-quickstart/auth/+merge/203631

(do not edit description out of merge proposal)

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

Affected files (+174, -31 lines):
   A [revision details]
   M quickstart/manage.py
   M quickstart/models/envs.py
   M quickstart/tests/cli/test_views.py
   M quickstart/tests/models/test_envs.py
   M quickstart/tests/test_manage.py

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

This branch is nice Brad, thank you.
LGTM with minor changes and a less trivial suggestion I'd like you to
consider.

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

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode294
quickstart/manage.py:294: At bootstrap, juju (v1.17 and later) writes
the admin-secret to a
IIRC Jenv files are generated starting form v1.16.

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode309
quickstart/manage.py:309: raise ValueError('admin-secret not found in
{}.'.format(filename))
Please encode('utf-8') the ValueError message.

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode316
quickstart/manage.py:316: - admin_secret: the password to use to access
the Juju API;
... or None if the admin-secret field is not found in the environments
YAML file... or similar?

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode419
quickstart/manage.py:419: options.admin_secret = _get_admin_secret(
It seems _get_admin_secret can raise exceptions we never catch. I am
considering a different strategy for this, please let me know what you
think about the following:
We always try to parse the jenv file, even if we already have an
admin_secret. Subsequently, we make the following check:
- if the password is found in the jenv, we use that, done.
- if the password is not found in the jenv:
   - if we already have an admin secret, we use that;
   - othrwise we exit with a (nice) error.
As a side note, this can involve moving the _get_admin_secret function
to the app module.
AFAICT this way we achieve two goals:
- we are still backward compatible with old versions of Juju;
- we support a use case we don't currently handle (in trunk): the case
when a user has a jenv enabled juju-core, runs quickstart to bootstrap
an environment, then runs quickstart interactively to explicitly provide
an admin-secret, and then re-invoke quickstart again on the same
environment (say, in order to deploy a bundle or reopen the GUI). Now
the passwords from the environments file and from the jenv are not the
same, but since we made the latter override the former, everything works
well.
How does it sound?

Another, less important, suggestion follows.
Rather than modifying options, we might just define admin_secret in this
scope, e.g.:

admin_secret = options.admin_secret
if admin_secret is None:
    admin_secret = ...

And then s/options.admin_secret/admin_secret/ below.
This way we keep the (very weak) separation between setup (which sets up
options) and run (which just uses them). <shrug>

https://codereview.appspot.com/57900043/diff/1/quickstart/models/envs.py
File quickstart/models/envs.py (right):

https://codereview.appspot.com/57900043/diff/1/quickstart/models/envs.py#newcode177
quickstart/models/envs.py:177: contents = _load_file(env_file)
Very nice.

https://codereview.appspot.com/57900043/

lp:~bac/juju-quickstart/auth updated
51. By Brad Crittenden

Code reorg from review. Handle exceptions more appropriately.

52. By Brad Crittenden

lint

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

Please take a look.

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

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode294
quickstart/manage.py:294: At bootstrap, juju (v1.17 and later) writes
the admin-secret to a
On 2014/01/29 11:48:40, frankban wrote:
> IIRC Jenv files are generated starting form v1.16.

Done.

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode309
quickstart/manage.py:309: raise ValueError('admin-secret not found in
{}.'.format(filename))
On 2014/01/29 11:48:40, frankban wrote:
> Please encode('utf-8') the ValueError message.

Done.

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode316
quickstart/manage.py:316: - admin_secret: the password to use to access
the Juju API;
On 2014/01/29 11:48:40, frankban wrote:
> ... or None if the admin-secret field is not found in the environments
YAML
> file... or similar?

Done.

https://codereview.appspot.com/57900043/diff/1/quickstart/manage.py#newcode419
quickstart/manage.py:419: options.admin_secret = _get_admin_secret(
Good idea. I've moved the definition of get_admin_secret to app.

https://codereview.appspot.com/57900043/

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

*** Submitted:

Get admin_secret from juju-generated jenv file.

As of Juju v 1.17 the admin-secret is stored in a generated file,
located in
$JUJU_HOME/environments/<environment>.jenv.

If an admin-secret is provided in environments.yaml, it is used and also
stored in the jenv file. If none is given, then juju fabricates one and
stores it in the jenv file.

The admin-secret *field* is marked as optional now. For the case where
a
local environment is used, Quickstart previously would generate an
admin-secret. That behavior is maintained by forcing 'admin-secret' to
be in
the missing_fields list. This approach ensures backwards compatibility.

Note that the Juju-generated admin-secret is not available until after
bootstrapping the environment. Due to this behavior, when using juju
v1.16
and not admin-secret is configured in the environments.yaml file, the
failure
will not be reported until after bootstrapping. This approach seemed
better
than introducing hard version checking.

R=frankban
CC=
https://codereview.appspot.com/57900043

https://codereview.appspot.com/57900043/

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-01-17 15:25:22 +0000
3+++ quickstart/app.py 2014-01-29 14:27:01 +0000
4@@ -35,6 +35,7 @@
5 utils,
6 watchers,
7 )
8+from quickstart.models import envs
9
10
11 class ProgramExit(Exception):
12@@ -496,3 +497,25 @@
13 env.deploy_bundle(bundle_yaml, name=bundle_name, bundle_id=bundle_id)
14 except jujuclient.EnvError as err:
15 raise ProgramExit('bad API server response: {}'.format(err.message))
16+
17+
18+def get_admin_secret(env_name, juju_home):
19+ """Read the admin-secret from the generated environment file.
20+
21+ At bootstrap, juju (v1.16 and later) writes the admin-secret to a
22+ generated file located in JUJU_HOME. Return the value.
23+ Raise a ValueError if:
24+ - the environment file is not found;
25+ - the environment file contents are not parsable by YAML;
26+ - the YAML contents are not properly structured;
27+ - the admin-secret is not found.
28+ """
29+ filename = '{}.jenv'.format(env_name)
30+ juju_env_file = os.path.expanduser(
31+ os.path.join(juju_home, 'environments', filename))
32+ jenv_db = envs.load_generated(juju_env_file)
33+ try:
34+ return jenv_db['admin-secret']
35+ except KeyError:
36+ msg = 'admin-secret not found in {}'.format(juju_env_file)
37+ raise ValueError(msg.encode('utf-8'))
38
39=== modified file 'quickstart/manage.py'
40--- quickstart/manage.py 2014-01-17 18:31:16 +0000
41+++ quickstart/manage.py 2014-01-29 14:27:01 +0000
42@@ -237,8 +237,10 @@
43 ' - using "juju switch" to select the default environment;\n'
44 ' - setting the default environment in {}.'.format(env_file)
45 )
46+
47 # Retrieve the environment database (or create an in-memory empty one).
48 env_db = _retrieve_env_db(parser, env_file if env_file_exists else None)
49+
50 # Validate the environment.
51 env_type_db = envs.get_env_type_db()
52 if interactive:
53@@ -250,7 +252,7 @@
54 # selected environment before proceeding.
55 env_data = _retrieve_env_data(parser, env_type_db, env_db, env_name)
56 # Update the options namespace with the new values.
57- options.admin_secret = env_data['admin-secret']
58+ options.admin_secret = env_data.get('admin-secret')
59 options.env_file = env_file
60 options.env_name = env_data['name']
61 options.env_type = env_data['type']
62@@ -290,7 +292,8 @@
63 """Set up the application options and logger.
64
65 Return the options as a namespace containing the following attributes:
66- - admin_secret: the password to use to access the Juju API;
67+ - admin_secret: the password to use to access the Juju API or None if
68+ no admin-secret is present in the $JUJU_HOME/environment.yaml file;
69 - bundle: the optional bundle (path or URL) to be deployed;
70 - charm_url: the Juju GUI charm URL or None if not specified;
71 - debug: whether debug mode is activated;
72@@ -389,10 +392,22 @@
73 already_bootstrapped, bsn_series = app.bootstrap(
74 options.env_name, is_local=is_local, debug=options.debug)
75
76+ # The admin secret was not in the environments file. Retrieve it from
77+ # the Juju-generated file.
78+ # Find the Juju-generated environment file.
79+ try:
80+ admin_secret = app.get_admin_secret(
81+ options.env_name, settings.JUJU_HOME)
82+ except ValueError as err:
83+ admin_secret = options.admin_secret
84+ if admin_secret is None:
85+ msg = err.message + ' or {}.'.format(options.env_file)
86+ raise app.ProgramExit(msg.encode('utf-8'))
87+
88 print('retrieving the Juju API address')
89 api_url = app.get_api_url(options.env_name)
90 print('connecting to {}'.format(api_url))
91- env = app.connect(api_url, options.admin_secret)
92+ env = app.connect(api_url, admin_secret)
93
94 # It is not possible to deploy on the bootstrap node if we are using the
95 # local provider, or if the bootstrap node series is not compatible with
96@@ -406,10 +421,10 @@
97 address = app.watch(env, unit_name)
98 env.close()
99 url = 'https://{}'.format(address)
100- print('url: {}\npassword: {}'.format(url, options.admin_secret))
101+ print('url: {}\npassword: {}'.format(url, admin_secret))
102 gui_api_url = 'wss://{}:443/ws'.format(address)
103 print('connecting to the Juju GUI server')
104- gui_env = app.connect(gui_api_url, options.admin_secret)
105+ gui_env = app.connect(gui_api_url, admin_secret)
106
107 # Handle bundle deployment.
108 if options.bundle is not None:
109
110=== modified file 'quickstart/models/envs.py'
111--- quickstart/models/envs.py 2014-01-20 12:02:10 +0000
112+++ quickstart/models/envs.py 2014-01-29 14:27:01 +0000
113@@ -134,6 +134,23 @@
114 return {'environments': {}}
115
116
117+def _load_file(env_file):
118+ """Load the given file and return the YAML-parsed contents."""
119+ # Load the Juju environments file.
120+ try:
121+ environments_file = open(env_file.encode('utf-8'))
122+ except IOError as err:
123+ msg = b'unable to open environments file: {}'.format(err)
124+ raise ValueError(msg)
125+ # Parse the Juju environments file.
126+ try:
127+ contents = serializers.yaml_load(environments_file)
128+ except Exception as err:
129+ msg = b'unable to parse environments file {}: {}'
130+ raise ValueError(msg.format(env_file.encode('utf-8'), err))
131+ return contents
132+
133+
134 def load(env_file):
135 """Load and parse the provided Juju environments.yaml file.
136
137@@ -157,24 +174,13 @@
138 - the environment file contents are not parsable by YAML;
139 - the YAML contents are not properly structured.
140 """
141- # Load the Juju environments file.
142- try:
143- environments_file = open(env_file.encode('utf-8'))
144- except IOError as err:
145- msg = b'unable to open environments file: {}'.format(err)
146- raise ValueError(msg)
147- # Parse the Juju environments file.
148- try:
149- contents = serializers.yaml_load(environments_file)
150- except Exception as err:
151- msg = b'unable to parse environments file {}: {}'
152- raise ValueError(msg.format(env_file.encode('utf-8'), err))
153+ contents = _load_file(env_file)
154 if contents is None:
155 return create_empty_env_db()
156 # Retrieve the environment list.
157 try:
158 env_contents = contents.get('environments', {}).items()
159- except AttributeError as err:
160+ except AttributeError:
161 msg = 'invalid YAML contents in {}: {}'.format(env_file, contents)
162 raise ValueError(msg.encode('utf-8'))
163 environments = {}
164@@ -193,6 +199,18 @@
165 return env_db
166
167
168+def load_generated(env_file, section='bootstrap-config'):
169+ """Given the path to a YAML file, load the file and return the section."""
170+ contents = _load_file(env_file)
171+ try:
172+ section_contents = contents[section]
173+ except (KeyError, TypeError):
174+ msg = 'invalid YAML contents in {}: {}'.format(env_file, contents)
175+ raise ValueError(msg.encode('utf-8'))
176+
177+ return section_contents
178+
179+
180 def save(env_file, env_db, backup_function=None):
181 """Save the given env_db to the provided environments.yaml file.
182
183@@ -331,8 +349,12 @@
184 # Retrieve a list of missing required fields.
185 missing_fields = [
186 field for field in env_metadata['fields']
187- if field.required and field.name not in env_data
188- ]
189+ if field.required and field.name not in env_data]
190+ # The following fields are not generally required but should be
191+ # automatically generated for a local environment.
192+ forced_to_be_generated = ('admin-secret', )
193+ missing_fields.extend([field for field in env_metadata['fields']
194+ if field.name in forced_to_be_generated])
195 # Assume all missing fields can be automatically generated.
196 env_data.update((field.name, field.generate()) for field in missing_fields)
197 return env_data
198@@ -375,7 +397,7 @@
199 'name', label='environment name', required=True,
200 help='the environment name to use with Juju (arbitrary string)')
201 admin_secret_field = fields.AutoGeneratedPasswordField(
202- 'admin-secret', label='admin secret', required=True,
203+ 'admin-secret', label='admin secret', required=False,
204 help='the password used to authenticate to the environment')
205 default_series_field = fields.ChoiceField(
206 'default-series', choices=settings.JUJU_DEFAULT_SERIES,
207@@ -636,7 +658,7 @@
208 then those keys are assigned a generic default field (fields.Field).
209 """
210 data = copy.deepcopy(env_data)
211- # Start building a list of (field, value) pairs preserving fields order.
212+ # Start building a list of (field, value) pairs preserving field order.
213 field_value_pairs = [
214 (field, data.pop(field.name, None))
215 for field in env_metadata['fields']
216
217=== modified file 'quickstart/tests/cli/test_views.py'
218--- quickstart/tests/cli/test_views.py 2014-01-16 11:17:42 +0000
219+++ quickstart/tests/cli/test_views.py 2014-01-29 14:27:01 +0000
220@@ -649,7 +649,7 @@
221
222 def test_save_invalid_form_data(self):
223 # Errors are displayed if the user tries to save invalid data.
224- changes = {'admin-secret': ''}
225+ changes = {'name': ''}
226 with self.patch_create_form(changes=changes) as mock_create_form:
227 self.call_view(env_name='ec2-west')
228 self.assertTrue(mock_create_form.called)
229@@ -661,7 +661,7 @@
230 # The form has been re-rendered passing the errors.
231 self.assertEqual(2, mock_create_form.call_count)
232 self.assertEqual(
233- {'admin-secret': 'a value is required for the admin secret field'},
234+ {'name': 'a value is required for the environment name field'},
235 mock_create_form.errors)
236
237 def test_save_invalid_new_name(self):
238
239=== modified file 'quickstart/tests/models/test_envs.py'
240--- quickstart/tests/models/test_envs.py 2014-01-20 12:02:10 +0000
241+++ quickstart/tests/models/test_envs.py 2014-01-29 14:27:01 +0000
242@@ -98,27 +98,32 @@
243 self.assertEqual({'environments': {}}, env_db)
244
245
246-class TestLoad(
247+class TestLoadFile(
248 helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin,
249 unittest.TestCase):
250
251 def test_no_file(self):
252 # A ValueError is raised if the environments file is not found.
253 expected = (
254- 'unable to open environments file: '
255+ "unable to open environments file: "
256 "[Errno 2] No such file or directory: '/no/such/file.yaml'"
257 )
258 with self.assert_value_error(expected):
259- envs.load('/no/such/file.yaml')
260+ envs._load_file('/no/such/file.yaml')
261
262 def test_invalid_yaml(self):
263 # A ValueError is raised if the environments file is not a valid YAML.
264 env_file = self.make_env_file(':')
265 with self.assertRaises(ValueError) as context_manager:
266- envs.load(env_file)
267+ envs._load_file(env_file)
268 expected = 'unable to parse environments file {}'.format(env_file)
269 self.assertIn(expected, bytes(context_manager.exception))
270
271+
272+class TestLoad(
273+ helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin,
274+ unittest.TestCase):
275+
276 def test_empty_file(self):
277 # An empty environments database is returned if the file is empty.
278 env_file = self.make_env_file('')
279@@ -199,6 +204,60 @@
280 self.assertEqual(expected, env_db)
281
282
283+class TestLoadGenerated(
284+ helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin,
285+ unittest.TestCase):
286+
287+ def test_empty_file(self):
288+ # A ValueError is raised if the environments file is empty.
289+ env_file = self.make_env_file('')
290+ expected = 'invalid YAML contents in {}: None'.format(env_file)
291+ with self.assert_value_error(expected):
292+ envs.load_generated(env_file)
293+
294+ def test_invalid_yaml_contents(self):
295+ # A ValueError is raised if the environments file is not well formed.
296+ env_file = self.make_env_file('a-string')
297+ expected = 'invalid YAML contents in {}: a-string'.format(env_file)
298+ with self.assert_value_error(expected):
299+ envs.load_generated(env_file)
300+
301+ def test_section_not_found(self):
302+ expected = {
303+ 'shoehorn-config': {
304+ 'admin-secret': 'Secret!',
305+ 'type': 'ec2'},
306+ }
307+ yaml_contents = expected.copy()
308+ env_file = self.make_env_file(yaml.safe_dump(yaml_contents))
309+ expected = 'invalid YAML contents in {}: {}'.format(
310+ env_file, yaml_contents)
311+ with self.assert_value_error(expected):
312+ envs.load_generated(env_file)
313+
314+ def test_successful_default_section(self):
315+ expected = {
316+ 'bootstrap-config': {
317+ 'admin-secret': 'Secret!',
318+ 'type': 'ec2'},
319+ }
320+ yaml_contents = expected.copy()
321+ env_file = self.make_env_file(yaml.safe_dump(yaml_contents))
322+ env_db = envs.load_generated(env_file)
323+ self.assertEqual(expected['bootstrap-config'], env_db)
324+
325+ def test_successful_specified_section(self):
326+ expected = {
327+ 'my-config': {
328+ 'admin-secret': 'Secret!',
329+ 'type': 'ec2'},
330+ }
331+ yaml_contents = expected.copy()
332+ env_file = self.make_env_file(yaml.safe_dump(yaml_contents))
333+ env_db = envs.load_generated(env_file, section='my-config')
334+ self.assertEqual(expected['my-config'], env_db)
335+
336+
337 class TestSave(helpers.EnvFileTestsMixin, unittest.TestCase):
338
339 def setUp(self):
340@@ -661,7 +720,7 @@
341 env_metadata = self.env_type_db['__fallback__']
342 expected = [
343 'type', 'name', 'admin-secret', 'default-series', 'is-default']
344- expected_required = ['type', 'name', 'admin-secret', 'is-default']
345+ expected_required = ['type', 'name', 'is-default']
346 self.assert_fields(expected, env_metadata)
347 self.assert_required_fields(expected_required, env_metadata)
348
349@@ -673,7 +732,7 @@
350 'type', 'name', 'admin-secret', 'default-series', 'root-dir',
351 'storage-port', 'shared-storage-port', 'network-bridge',
352 'is-default']
353- expected_required = ['type', 'name', 'admin-secret', 'is-default']
354+ expected_required = ['type', 'name', 'is-default']
355 self.assert_fields(expected, env_metadata)
356 self.assert_required_fields(expected_required, env_metadata)
357
358@@ -685,7 +744,7 @@
359 'type', 'name', 'admin-secret', 'default-series', 'access-key',
360 'secret-key', 'control-bucket', 'region', 'is-default']
361 expected_required = [
362- 'type', 'name', 'admin-secret', 'access-key', 'secret-key',
363+ 'type', 'name', 'access-key', 'secret-key',
364 'control-bucket', 'is-default']
365 self.assert_fields(expected, env_metadata)
366 self.assert_required_fields(expected_required, env_metadata)
367@@ -700,7 +759,7 @@
368 'region', 'auth-mode', 'username', 'password', 'access-key',
369 'secret-key', 'is-default']
370 expected_required = [
371- 'type', 'name', 'admin-secret', 'use-floating-ip',
372+ 'type', 'name', 'use-floating-ip',
373 'control-bucket', 'auth-url', 'tenant-name', 'region',
374 'is-default']
375 self.assert_fields(expected, env_metadata)
376@@ -715,7 +774,7 @@
377 'management-subscription-id', 'management-certificate-path',
378 'storage-account-name', 'is-default']
379 expected_required = [
380- 'type', 'name', 'admin-secret', 'location',
381+ 'type', 'name', 'location',
382 'management-subscription-id', 'management-certificate-path',
383 'storage-account-name', 'is-default']
384 self.assert_fields(expected, env_metadata)
385
386=== modified file 'quickstart/tests/test_app.py'
387--- quickstart/tests/test_app.py 2014-01-17 14:05:03 +0000
388+++ quickstart/tests/test_app.py 2014-01-29 14:27:01 +0000
389@@ -1311,3 +1311,23 @@
390 with self.assertRaises(ValueError) as context_manager:
391 app.deploy_bundle(env, self.yaml, self.name, None)
392 self.assertIs(error, context_manager.exception)
393+
394+
395+class TestGetAdminSecret(unittest.TestCase):
396+
397+ def test_no_admin_secret(self):
398+ with mock.patch('quickstart.manage.envs.load_generated',
399+ lambda x: {}):
400+ with self.assertRaises(ValueError) as exc:
401+ app.get_admin_secret('local', '/home/bac/.juju')
402+ expected = (
403+ u'admin-secret not found in '
404+ '/home/bac/.juju/environments/local.jenv')
405+ self.assertIn(expected, bytes(exc.exception))
406+
407+ def test_success(self):
408+ expected = 'superchunk'
409+ with mock.patch('quickstart.manage.envs.load_generated',
410+ lambda x: {'admin-secret': expected}):
411+ secret = app.get_admin_secret('local', '~bac/.juju')
412+ self.assertEqual(expected, secret)
413
414=== modified file 'quickstart/tests/test_manage.py'
415--- quickstart/tests/test_manage.py 2014-01-13 15:46:27 +0000
416+++ quickstart/tests/test_manage.py 2014-01-29 14:27:01 +0000
417@@ -38,6 +38,7 @@
418 from quickstart.cli import views
419 from quickstart.models import envs
420 from quickstart.tests import helpers
421+from quickstart import app
422
423
424 class TestDescriptionAction(unittest.TestCase):
425@@ -393,8 +394,7 @@
426 self.parser, self.env_type_db, self.env_db, 'local-with-errors')
427 self.parser.error.assert_called_once_with(
428 'cannot use the local-with-errors environment:\n'
429- 'the storage port field requires an integer value\n'
430- 'a value is required for the admin secret field')
431+ 'the storage port field requires an integer value')
432
433
434 class TestSetupEnv(helpers.EnvFileTestsMixin, unittest.TestCase):
435@@ -658,12 +658,24 @@
436 options.update(kwargs)
437 return mock.Mock(**options)
438
439+ @staticmethod
440+ def mock_get_admin_secret_success(name, home):
441+ return 'jenv secret'
442+
443+ @staticmethod
444+ def mock_get_admin_secret_error(name, home):
445+ fn = '{}.jenv'.format(name)
446+ path = os.path.join(home, 'environments', fn)
447+ msg = 'admin-secret not found in {}'.format(path)
448+ raise ValueError(msg.encode('utf-8'))
449+
450 def test_no_bundle(self, mock_app, mock_open):
451 # The application runs correctly if no bundle is provided.
452 token = 'AUTHTOKEN'
453 mock_app.create_auth_token.return_value = token
454 mock_app.watch.return_value = '1.2.3.4'
455 mock_app.bootstrap.return_value = (True, 'precise')
456+ mock_app.get_admin_secret = self.mock_get_admin_secret_error
457 options = self.make_options()
458 manage.run(options)
459 mock_app.ensure_dependencies.assert_called()
460@@ -722,3 +734,41 @@
461 options = self.make_options(open_browser=False)
462 manage.run(options)
463 self.assertFalse(mock_open.called)
464+
465+ def test_admin_secret_fetched(self, mock_app, mock_open):
466+ # If an admin secret is fetched from jenv it is used, even if one is
467+ # found in environments.yaml, as set in options.admin_secret.
468+ mock_app.get_admin_secret = self.mock_get_admin_secret_success
469+ mock_app.bootstrap.return_value = (True, 'precise')
470+ options = self.make_options(admin_secret='secret in environments.yaml')
471+ manage.run(options)
472+ mock_app.connect.assert_has_calls([
473+ mock.call(mock_app.get_api_url(), 'jenv secret'),
474+ ])
475+
476+ def test_admin_secret_from_environments_yaml(self, mock_app, mock_open):
477+ # If an admin secret is not fetched from jenv, then the one from
478+ # environments.yaml is used, as found in options.admin_secret.
479+ mock_app.get_admin_secret = self.mock_get_admin_secret_error
480+ mock_app.bootstrap.return_value = (True, 'precise')
481+ options = self.make_options(admin_secret='secret in environments.yaml')
482+ manage.run(options)
483+ mock_app.connect.assert_has_calls([
484+ mock.call(mock_app.get_api_url(), 'secret in environments.yaml'),
485+ ])
486+
487+ def test_no_admin_secret_found(self, mock_app, mock_open):
488+ # If admin-secret cannot be found anywhere a ProgramExit is called.
489+ mock_app.ProgramExit = app.ProgramExit
490+ mock_app.get_admin_secret = self.mock_get_admin_secret_error
491+ mock_app.bootstrap.return_value = (True, 'precise')
492+ options = self.make_options(
493+ admin_secret=None,
494+ env_name='local',
495+ env_file='environments.yaml')
496+ with self.assertRaises(app.ProgramExit) as context:
497+ manage.run(options)
498+ expected = (
499+ u'admin-secret not found in ~/.juju/environments/local.jenv '
500+ 'or environments.yaml.')
501+ self.assertEqual(expected, context.exception.message)

Subscribers

People subscribed via source and target branches