Merge lp:~frankban/juju-gui/quickstart-bootstrap into lp:juju-gui/juju-quickstart
- quickstart-bootstrap
- Merge into juju-quickstart
Status: | Merged |
---|---|
Merged at revision: | 3 |
Proposed branch: | lp:~frankban/juju-gui/quickstart-bootstrap |
Merge into: | lp:juju-gui/juju-quickstart |
Diff against target: |
780 lines (+634/-23) 9 files modified
Makefile (+7/-9) juju-quickstart (+10/-2) quickstart/app.py (+42/-0) quickstart/manage.py (+67/-12) quickstart/tests/helpers.py (+60/-0) quickstart/tests/test_app.py (+29/-0) quickstart/tests/test_manage.py (+131/-0) quickstart/tests/test_utils.py (+178/-0) quickstart/utils.py (+110/-0) |
To merge this branch: | bzr merge lp:~frankban/juju-gui/quickstart-bootstrap |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+191234@code.launchpad.net |
Commit message
Description of the change
Parse and validate the environment file/options.
Check if the given environment name exists,
ensure it does not use the local provider,
ensure it includes an admin-secret,
retrieve the admin-secret.
Tests: make check
Francesco Banconi (frankban) wrote : | # |
Gary Poster (gary) wrote : | # |
LGTM with a few trivials. Thank you.
https:/
File Makefile (right):
https:/
Makefile:20: JUJU_ENV ?= quickstart
So...this is used by make run to override the normal Python logic of the
command. I'm not quite sure of the value, but I'm fine with it.
https:/
File quickstart/
https:/
quickstart/
plugin."""
Trivial: Test helpers
https:/
File quickstart/
https:/
quickstart/
simulate an arbitrary environment name.
trivial: and it is
https:/
File quickstart/
https:/
quickstart/
checking precise non-zero retcode seems a bit risky, even though it
probably is practically fine for something as ancient as ls.
https:/
quickstart/
likewise
https:/
quickstart/
output=
lol
https:/
File quickstart/utils.py (right):
https:/
quickstart/
I wonder which is more fragile: relying on a regex of a stdout reply, or
looking at ~/.juju/
~/.juju/
suppose that's a compelling argument, given that the answer to my
question is not obvious to me.
Madison Scott-Clary (makyo) wrote : | # |
LGTM, thank you!
- 9. By Francesco Banconi
-
Changes as per review.
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Parse and validate the environment file/options.
Check if the given environment name exists,
ensure it does not use the local provider,
ensure it includes an admin-secret,
retrieve the admin-secret.
Tests: make check
R=gary.poster, matthew.scott
CC=
https:/
https:/
File Makefile (right):
https:/
Makefile:20: JUJU_ENV ?= quickstart
On 2013/10/15 17:02:11, gary.poster wrote:
> So...this is used by make run to override the normal Python logic of
the
> command. I'm not quite sure of the value, but I'm fine with it.
You are right, since JUJU_ENV and the default env are already handled by
the application, this no longer makes sense. Fixed.
https:/
File quickstart/
https:/
quickstart/
plugin."""
On 2013/10/15 17:02:11, gary.poster wrote:
> Trivial: Test helpers
Done.
https:/
File quickstart/
https:/
quickstart/
simulate an arbitrary environment name.
On 2013/10/15 17:02:11, gary.poster wrote:
> trivial: and it is
Done.
https:/
File quickstart/
https:/
quickstart/
On 2013/10/15 17:02:11, gary.poster wrote:
> checking precise non-zero retcode seems a bit risky, even though it
probably is
> practically fine for something as ancient as ls.
Good suggestion, fixed.
https:/
quickstart/
On 2013/10/15 17:02:11, gary.poster wrote:
> likewise
In this case this is safe because we return 127 ourselves.
https:/
File quickstart/utils.py (right):
https:/
quickstart/
On 2013/10/15 17:02:11, gary.poster wrote:
> I wonder which is more fragile: relying on a regex of a stdout reply,
or looking
> at ~/.juju/
This is
> certainly easier, so I suppose that's a compelling argument, given
that the
> answer to my question is not obvious to me.
As discussed, I will ask juju-core devs about
~/.juju/
--format json" a good idea?).
Francesco Banconi (frankban) wrote : | # |
Thank you Gary and Matthew!
Preview Diff
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2013-10-14 15:54:00 +0000 | |||
3 | +++ Makefile 2013-10-16 07:23:37 +0000 | |||
4 | @@ -15,10 +15,6 @@ | |||
5 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
6 | 16 | 16 | ||
7 | 17 | PYTHON = python | 17 | PYTHON = python |
8 | 18 | |||
9 | 19 | # Define the default Juju environment used by "make run". | ||
10 | 20 | JUJU_ENV ?= local | ||
11 | 21 | |||
12 | 22 | SYSDEPS = build-essential python-pip python-virtualenv | 18 | SYSDEPS = build-essential python-pip python-virtualenv |
13 | 23 | 19 | ||
14 | 24 | VENV = .venv | 20 | VENV = .venv |
15 | @@ -36,7 +32,7 @@ | |||
16 | 36 | 32 | ||
17 | 37 | clean: | 33 | clean: |
18 | 38 | $(PYTHON) setup.py clean | 34 | $(PYTHON) setup.py clean |
20 | 39 | rm -rfv build/ dist/ ./juju_quickstart.egg-info MANIFEST | 35 | rm -rfv build/ dist/ juju_quickstart.egg-info MANIFEST |
21 | 40 | rm -rfv $(VENV) | 36 | rm -rfv $(VENV) |
22 | 41 | find . -name '*.pyc' -delete | 37 | find . -name '*.pyc' -delete |
23 | 42 | find . -name '__pycache__' -type d -delete | 38 | find . -name '__pycache__' -type d -delete |
24 | @@ -53,9 +49,10 @@ | |||
25 | 53 | @echo 'make source - Create source package.' | 49 | @echo 'make source - Create source package.' |
26 | 54 | @echo 'make install - Install on local system.' | 50 | @echo 'make install - Install on local system.' |
27 | 55 | @echo 'make run - Run the application in the development environment.\n' | 51 | @echo 'make run - Run the application in the development environment.\n' |
31 | 56 | @echo ' An environment named "local" is used by default.' | 52 | @echo ' If "juju switch" has been used to set a default environment, that' |
32 | 57 | @echo ' Define the JUJU_ENV environment variable to select another' | 53 | @echo ' environment will be used. It is possible to override the default' |
33 | 58 | @echo ' environment, e.g.: "make run JUJU_ENV=ec2".' | 54 | @echo ' Juju environment by setting the JUJU_ENV environment variable,' |
34 | 55 | @echo ' e.g.: "make run JUJU_ENV=ec2".' | ||
35 | 59 | @echo 'make clean - Get rid of bytecode files, build and dist dirs, venv.' | 56 | @echo 'make clean - Get rid of bytecode files, build and dist dirs, venv.' |
36 | 60 | 57 | ||
37 | 61 | install: | 58 | install: |
38 | @@ -66,7 +63,7 @@ | |||
39 | 66 | @$(VENV)/bin/flake8 --show-source --exclude=$(VENV) ./quickstart | 63 | @$(VENV)/bin/flake8 --show-source --exclude=$(VENV) ./quickstart |
40 | 67 | 64 | ||
41 | 68 | run: setup | 65 | run: setup |
43 | 69 | $(VENV)/bin/python ./juju-quickstart --environment $(JUJU_ENV) | 66 | $(VENV)/bin/python ./juju-quickstart |
44 | 70 | 67 | ||
45 | 71 | source: | 68 | source: |
46 | 72 | $(PYTHON) setup.py sdist | 69 | $(PYTHON) setup.py sdist |
47 | @@ -77,5 +74,6 @@ | |||
48 | 77 | test: setup | 74 | test: setup |
49 | 78 | @$(VENV)/bin/nosetests -s --verbosity=2 \ | 75 | @$(VENV)/bin/nosetests -s --verbosity=2 \ |
50 | 79 | --with-coverage --cover-package=quickstart quickstart | 76 | --with-coverage --cover-package=quickstart quickstart |
51 | 77 | @rm .coverage | ||
52 | 80 | 78 | ||
53 | 81 | .PHONY: all clean check help install lint run setup source sysdeps test | 79 | .PHONY: all clean check help install lint run setup source sysdeps test |
54 | 82 | 80 | ||
55 | === modified file 'juju-quickstart' | |||
56 | --- juju-quickstart 2013-10-14 15:24:29 +0000 | |||
57 | +++ juju-quickstart 2013-10-16 07:23:37 +0000 | |||
58 | @@ -18,9 +18,17 @@ | |||
59 | 18 | 18 | ||
60 | 19 | """Juju Quickstart plugin entry point.""" | 19 | """Juju Quickstart plugin entry point.""" |
61 | 20 | 20 | ||
63 | 21 | from quickstart import manage | 21 | import sys |
64 | 22 | |||
65 | 23 | from quickstart import ( | ||
66 | 24 | app, | ||
67 | 25 | manage, | ||
68 | 26 | ) | ||
69 | 22 | 27 | ||
70 | 23 | 28 | ||
71 | 24 | if __name__ == '__main__': | 29 | if __name__ == '__main__': |
72 | 25 | options = manage.setup() | 30 | options = manage.setup() |
74 | 26 | manage.run(options) | 31 | try: |
75 | 32 | manage.run(options) | ||
76 | 33 | except app.ProgramExit as err: | ||
77 | 34 | sys.exit(str(err)) | ||
78 | 27 | 35 | ||
79 | === added file 'quickstart/app.py' | |||
80 | --- quickstart/app.py 1970-01-01 00:00:00 +0000 | |||
81 | +++ quickstart/app.py 2013-10-16 07:23:37 +0000 | |||
82 | @@ -0,0 +1,42 @@ | |||
83 | 1 | # This file is part of the Juju GUI, which lets users view and manage Juju | ||
84 | 2 | # environments within a graphical interface (https://launchpad.net/juju-gui). | ||
85 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
86 | 4 | # | ||
87 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
88 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
89 | 7 | # the Free Software Foundation. | ||
90 | 8 | # | ||
91 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
92 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
93 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
94 | 12 | # Affero General Public License for more details. | ||
95 | 13 | # | ||
96 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
97 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
98 | 16 | |||
99 | 17 | """Juju Quickstart base application functions.""" | ||
100 | 18 | |||
101 | 19 | |||
102 | 20 | class ProgramExit(Exception): | ||
103 | 21 | """An error occurred while setting up the Juju environment. | ||
104 | 22 | |||
105 | 23 | Raise this exception if you want the program to exit gracefully printing | ||
106 | 24 | the error message to stderr. | ||
107 | 25 | """ | ||
108 | 26 | |||
109 | 27 | def __init__(self, message): | ||
110 | 28 | self.message = message | ||
111 | 29 | |||
112 | 30 | def __str__(self): | ||
113 | 31 | return 'juju-quickstart: error: {}'.format(self.message) | ||
114 | 32 | |||
115 | 33 | |||
116 | 34 | def bootstrap(env_name): | ||
117 | 35 | """Bootstrap the Juju environment with the given name. | ||
118 | 36 | |||
119 | 37 | Return the environment API address (e.g. "api.example.com:17070"). | ||
120 | 38 | Raise a ProgramExit if any error occurs in the bootstrap process. | ||
121 | 39 | Otherwise return when the environment is bootstrapped and the API server | ||
122 | 40 | is ready to accept connections. | ||
123 | 41 | """ | ||
124 | 42 | # TODO: everything! | ||
125 | 0 | 43 | ||
126 | === modified file 'quickstart/manage.py' | |||
127 | --- quickstart/manage.py 2013-10-14 15:24:29 +0000 | |||
128 | +++ quickstart/manage.py 2013-10-16 07:23:37 +0000 | |||
129 | @@ -21,9 +21,13 @@ | |||
130 | 21 | import os | 21 | import os |
131 | 22 | 22 | ||
132 | 23 | import quickstart | 23 | import quickstart |
136 | 24 | 24 | from quickstart import ( | |
137 | 25 | 25 | app, | |
138 | 26 | default_envfile = os.path.expanduser('~/.juju/environments.yaml') | 26 | utils, |
139 | 27 | ) | ||
140 | 28 | |||
141 | 29 | |||
142 | 30 | juju_home = os.getenv('JUJU_HOME', '~/.juju') | ||
143 | 27 | description = 'set up a Juju environment (including the GUI) in very few steps' | 31 | description = 'set up a Juju environment (including the GUI) in very few steps' |
144 | 28 | version = quickstart.get_version() | 32 | version = quickstart.get_version() |
145 | 29 | 33 | ||
146 | @@ -36,26 +40,77 @@ | |||
147 | 36 | parser.exit() | 40 | parser.exit() |
148 | 37 | 41 | ||
149 | 38 | 42 | ||
150 | 43 | def _validate_env(options, parser): | ||
151 | 44 | """Validate and process the provided environment related options. | ||
152 | 45 | |||
153 | 46 | Exit with an error if options are not valid. | ||
154 | 47 | """ | ||
155 | 48 | env_file = os.path.abspath(os.path.expanduser(options.env_file)) | ||
156 | 49 | # Validate the environment name. | ||
157 | 50 | env_name = options.env_name | ||
158 | 51 | if env_name is None: | ||
159 | 52 | return parser.error( | ||
160 | 53 | 'unable to find an environment name to use\n' | ||
161 | 54 | 'It is possible to specify the environment name by either:\n' | ||
162 | 55 | ' - passing the -e or --environment argument;\n' | ||
163 | 56 | ' - setting the JUJU_ENV environment variable;\n' | ||
164 | 57 | ' - using "juju switch" to select the default environment to use.' | ||
165 | 58 | ) | ||
166 | 59 | # Validate the environment file. | ||
167 | 60 | try: | ||
168 | 61 | env_type, admin_secret = utils.parse_env_file(env_file, env_name) | ||
169 | 62 | except ValueError as err: | ||
170 | 63 | return parser.error(str(err)) | ||
171 | 64 | # XXX 2013-10-15 frankban: add support for local providers. | ||
172 | 65 | if env_type == 'local': | ||
173 | 66 | return parser.error('the local provider is not currently supported') | ||
174 | 67 | # Update the options namespace with the new values. | ||
175 | 68 | options.admin_secret = admin_secret | ||
176 | 69 | options.env_file = env_file | ||
177 | 70 | options.env_type = env_type | ||
178 | 71 | |||
179 | 72 | |||
180 | 39 | def setup(): | 73 | def setup(): |
182 | 40 | """Set up the application options.""" | 74 | """Set up the application options. |
183 | 75 | |||
184 | 76 | Return the options as a namespace containing the followin attributes: | ||
185 | 77 | - admin_secret: the password to use to access the Juju API; | ||
186 | 78 | - env_file: the absolute path of the Juju environments.yaml file; | ||
187 | 79 | - env_name: the name of the Juju environment to use; | ||
188 | 80 | - env_type: the provider type of the selected Juju environment. | ||
189 | 81 | |||
190 | 82 | Exit with an error if the provided arguments are not valid. | ||
191 | 83 | """ | ||
192 | 84 | default_env_name = utils.get_default_env_name() | ||
193 | 85 | # Define the help message for the --environment option. | ||
194 | 86 | env_help = 'the name of the Juju environment to use' | ||
195 | 87 | if default_env_name is not None: | ||
196 | 88 | env_help = '{} (%(default)s)'.format(env_help) | ||
197 | 89 | # Create and set up the arguments parser. | ||
198 | 41 | parser = argparse.ArgumentParser(description=quickstart.__doc__) | 90 | parser = argparse.ArgumentParser(description=quickstart.__doc__) |
199 | 42 | parser.add_argument( | 91 | parser.add_argument( |
202 | 43 | '-e', '--environment', required=True, dest='env_name', | 92 | '-e', '--environment', default=default_env_name, dest='env_name', |
203 | 44 | help='the name of the Juju environment to use') | 93 | help=env_help) |
204 | 45 | parser.add_argument( | 94 | parser.add_argument( |
208 | 46 | '--environments-file', dest='env_file', type=file, | 95 | '--environments-file', |
209 | 47 | default=default_envfile, | 96 | default=os.path.join(juju_home, 'environments.yaml'), dest='env_file', |
210 | 48 | help='the path to the Juju environments YAML file') | 97 | help='the path to the Juju environments YAML file (%(default)s)') |
211 | 49 | parser.add_argument( | 98 | parser.add_argument( |
212 | 50 | '--version', action='version', version='%(prog)s {}'.format(version)) | 99 | '--version', action='version', version='%(prog)s {}'.format(version)) |
213 | 51 | # This is required by juju-core: see "juju help plugins". | 100 | # This is required by juju-core: see "juju help plugins". |
214 | 52 | parser.add_argument( | 101 | parser.add_argument( |
215 | 53 | '--description', action=_DescriptionAction, default=argparse.SUPPRESS, | 102 | '--description', action=_DescriptionAction, default=argparse.SUPPRESS, |
216 | 54 | nargs=0, help="show program's description and exit") | 103 | nargs=0, help="show program's description and exit") |
218 | 55 | return parser.parse_args() | 104 | # Parse the provided arguments. |
219 | 105 | options = parser.parse_args() | ||
220 | 106 | # Validate and process the provided arguments. | ||
221 | 107 | _validate_env(options, parser) | ||
222 | 108 | return options | ||
223 | 56 | 109 | ||
224 | 57 | 110 | ||
225 | 58 | def run(options): | 111 | def run(options): |
227 | 59 | """Run the application""" | 112 | """Run the application.""" |
228 | 60 | print('juju quickstart v{}'.format(version)) | 113 | print('juju quickstart v{}'.format(version)) |
230 | 61 | # TODO: everything! | 114 | print('bootstrapping the {} environment (type: {})'.format( |
231 | 115 | options.env_name, options.env_type)) | ||
232 | 116 | app.bootstrap(options.env_name) | ||
233 | 62 | 117 | ||
234 | === added file 'quickstart/tests/helpers.py' | |||
235 | --- quickstart/tests/helpers.py 1970-01-01 00:00:00 +0000 | |||
236 | +++ quickstart/tests/helpers.py 2013-10-16 07:23:37 +0000 | |||
237 | @@ -0,0 +1,60 @@ | |||
238 | 1 | # This file is part of the Juju GUI, which lets users view and manage Juju | ||
239 | 2 | # environments within a graphical interface (https://launchpad.net/juju-gui). | ||
240 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
241 | 4 | # | ||
242 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
243 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
244 | 7 | # the Free Software Foundation. | ||
245 | 8 | # | ||
246 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
247 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
248 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
249 | 12 | # Affero General Public License for more details. | ||
250 | 13 | # | ||
251 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
252 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
253 | 16 | |||
254 | 17 | """Test helpers for the Juju Quickstart plugin.""" | ||
255 | 18 | |||
256 | 19 | from contextlib import contextmanager | ||
257 | 20 | import os | ||
258 | 21 | import tempfile | ||
259 | 22 | |||
260 | 23 | import yaml | ||
261 | 24 | |||
262 | 25 | |||
263 | 26 | class ValueErrorTestsMixin(object): | ||
264 | 27 | """Set up some base methods for testing functions raising ValueErrors.""" | ||
265 | 28 | |||
266 | 29 | @contextmanager | ||
267 | 30 | def assert_value_error(self, error): | ||
268 | 31 | """Ensure a ValueError is raised in the context block. | ||
269 | 32 | |||
270 | 33 | Also check that the exception includes the expected error message. | ||
271 | 34 | """ | ||
272 | 35 | with self.assertRaises(ValueError) as context_manager: | ||
273 | 36 | yield | ||
274 | 37 | self.assertEqual(error, str(context_manager.exception)) | ||
275 | 38 | |||
276 | 39 | |||
277 | 40 | class EnvFileTestsMixin(object): | ||
278 | 41 | """Shared methods for testing a Juju environments file.""" | ||
279 | 42 | |||
280 | 43 | valid_contents = yaml.safe_dump({ | ||
281 | 44 | 'environments': {'aws': {'admin-secret': 'Secret!', 'type': 'ec2'}}, | ||
282 | 45 | }) | ||
283 | 46 | |||
284 | 47 | def make_env_file(self, contents=None): | ||
285 | 48 | """Create a Juju environments file containing the given contents. | ||
286 | 49 | |||
287 | 50 | If contents is None, use the valid environment contents defined in | ||
288 | 51 | self.valid_contents. | ||
289 | 52 | Return the environments file path. | ||
290 | 53 | """ | ||
291 | 54 | if contents is None: | ||
292 | 55 | contents = self.valid_contents | ||
293 | 56 | env_file = tempfile.NamedTemporaryFile(delete=False) | ||
294 | 57 | self.addCleanup(os.remove, env_file.name) | ||
295 | 58 | env_file.write(contents) | ||
296 | 59 | env_file.close() | ||
297 | 60 | return env_file.name | ||
298 | 0 | 61 | ||
299 | === added file 'quickstart/tests/test_app.py' | |||
300 | --- quickstart/tests/test_app.py 1970-01-01 00:00:00 +0000 | |||
301 | +++ quickstart/tests/test_app.py 2013-10-16 07:23:37 +0000 | |||
302 | @@ -0,0 +1,29 @@ | |||
303 | 1 | # This file is part of the Juju GUI, which lets users view and manage Juju | ||
304 | 2 | # environments within a graphical interface (https://launchpad.net/juju-gui). | ||
305 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
306 | 4 | # | ||
307 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
308 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
309 | 7 | # the Free Software Foundation. | ||
310 | 8 | # | ||
311 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
312 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
313 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
314 | 12 | # Affero General Public License for more details. | ||
315 | 13 | # | ||
316 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
317 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
318 | 16 | |||
319 | 17 | """Tests for the Juju Quickstart base application functions.""" | ||
320 | 18 | |||
321 | 19 | import unittest | ||
322 | 20 | |||
323 | 21 | from quickstart import app | ||
324 | 22 | |||
325 | 23 | |||
326 | 24 | class TestProgramExit(unittest.TestCase): | ||
327 | 25 | |||
328 | 26 | def test_string_representation(self): | ||
329 | 27 | # The error is properly represented as a string. | ||
330 | 28 | exception = app.ProgramExit('bad wolf') | ||
331 | 29 | self.assertEqual('juju-quickstart: error: bad wolf', str(exception)) | ||
332 | 0 | 30 | ||
333 | === modified file 'quickstart/tests/test_manage.py' | |||
334 | --- quickstart/tests/test_manage.py 2013-10-14 15:24:29 +0000 | |||
335 | +++ quickstart/tests/test_manage.py 2013-10-16 07:23:37 +0000 | |||
336 | @@ -17,11 +17,15 @@ | |||
337 | 17 | """Tests for the Juju Quickstart management infrastructure.""" | 17 | """Tests for the Juju Quickstart management infrastructure.""" |
338 | 18 | 18 | ||
339 | 19 | import argparse | 19 | import argparse |
340 | 20 | import os | ||
341 | 20 | import unittest | 21 | import unittest |
342 | 21 | 22 | ||
343 | 22 | import mock | 23 | import mock |
344 | 24 | import yaml | ||
345 | 23 | 25 | ||
346 | 26 | import quickstart | ||
347 | 24 | from quickstart import manage | 27 | from quickstart import manage |
348 | 28 | from quickstart.tests import helpers | ||
349 | 25 | 29 | ||
350 | 26 | 30 | ||
351 | 27 | class TestDescriptionAction(unittest.TestCase): | 31 | class TestDescriptionAction(unittest.TestCase): |
352 | @@ -39,3 +43,130 @@ | |||
353 | 39 | self.assertIsNone(args.test) | 43 | self.assertIsNone(args.test) |
354 | 40 | mock_print.assert_called_once_with(manage.description) | 44 | mock_print.assert_called_once_with(manage.description) |
355 | 41 | mock_exit.assert_called_once_with(0) | 45 | mock_exit.assert_called_once_with(0) |
356 | 46 | |||
357 | 47 | |||
358 | 48 | class TestValidateEnv(helpers.EnvFileTestsMixin, unittest.TestCase): | ||
359 | 49 | |||
360 | 50 | def setUp(self): | ||
361 | 51 | self.parser = mock.Mock() | ||
362 | 52 | |||
363 | 53 | def make_options(self, env_file, env_name=None): | ||
364 | 54 | """Return a mock options object which includes the passed arguments.""" | ||
365 | 55 | return mock.Mock(env_file=env_file, env_name=env_name) | ||
366 | 56 | |||
367 | 57 | def test_resulting_options(self): | ||
368 | 58 | # The options object is correctly set up. | ||
369 | 59 | env_file = self.make_env_file() | ||
370 | 60 | options = self.make_options(env_file, env_name='aws') | ||
371 | 61 | manage._validate_env(options, self.parser) | ||
372 | 62 | self.assertEqual('Secret!', options.admin_secret) | ||
373 | 63 | self.assertEqual(env_file, options.env_file) | ||
374 | 64 | self.assertEqual('aws', options.env_name) | ||
375 | 65 | self.assertEqual('ec2', options.env_type) | ||
376 | 66 | |||
377 | 67 | def test_expand_user(self): | ||
378 | 68 | # The ~ construct is correctly expanded in the validation process. | ||
379 | 69 | env_file = self.make_env_file() | ||
380 | 70 | # Split the full path of the env file, e.g. from a full "/tmp/env.file" | ||
381 | 71 | # path retrieve the base path "/tmp" and the file name "env.file". | ||
382 | 72 | # By doing that we can simulate that the user's home is "/tmp" and that | ||
383 | 73 | # the env file is "~/env.file". | ||
384 | 74 | base_path, filename = os.path.split(env_file) | ||
385 | 75 | path = '~/{}'.format(filename) | ||
386 | 76 | options = self.make_options(env_file=path, env_name='aws') | ||
387 | 77 | with mock.patch('os.environ', {'HOME': base_path}): | ||
388 | 78 | manage._validate_env(options, self.parser) | ||
389 | 79 | self.assertEqual(env_file, options.env_file) | ||
390 | 80 | |||
391 | 81 | def test_no_env_name(self): | ||
392 | 82 | # A parser error is invoked if the environment name is missing. | ||
393 | 83 | options = self.make_options(self.make_env_file()) | ||
394 | 84 | manage._validate_env(options, self.parser) | ||
395 | 85 | self.assertTrue(self.parser.error.called) | ||
396 | 86 | message = self.parser.error.call_args[0][0] | ||
397 | 87 | self.assertIn('unable to find an environment name to use', message) | ||
398 | 88 | |||
399 | 89 | def test_error_parsing_env_file(self): | ||
400 | 90 | # A parser error is invoked if an error occurs parsing the env file. | ||
401 | 91 | env_file = self.make_env_file() | ||
402 | 92 | options = self.make_options(env_file, env_name='no-such') | ||
403 | 93 | manage._validate_env(options, self.parser) | ||
404 | 94 | expected = 'environment no-such not found in {}'.format(env_file) | ||
405 | 95 | self.parser.error.assert_called_once_with(expected) | ||
406 | 96 | |||
407 | 97 | def test_local_provider(self): | ||
408 | 98 | # The parser exits with an error if the provided environment name | ||
409 | 99 | # refers to a local provider type. | ||
410 | 100 | contents = yaml.safe_dump({ | ||
411 | 101 | 'environments': { | ||
412 | 102 | 'lxc': {'admin-secret': 'Secret!', 'type': 'local'}, | ||
413 | 103 | }, | ||
414 | 104 | }) | ||
415 | 105 | env_file = self.make_env_file(contents) | ||
416 | 106 | options = self.make_options(env_file, env_name='lxc') | ||
417 | 107 | manage._validate_env(options, self.parser) | ||
418 | 108 | expected = 'the local provider is not currently supported' | ||
419 | 109 | self.parser.error.assert_called_once_with(expected) | ||
420 | 110 | |||
421 | 111 | |||
422 | 112 | @mock.patch('quickstart.manage._validate_env', mock.Mock()) | ||
423 | 113 | class TestSetup(unittest.TestCase): | ||
424 | 114 | |||
425 | 115 | def patch_get_default_env_name(self, env_name=None): | ||
426 | 116 | """Patch the function used by setup() to retrieve the default env name. | ||
427 | 117 | |||
428 | 118 | This way the test does not rely on the user's Juju environment set up, | ||
429 | 119 | and it is also possible to simulate an arbitrary environment name. | ||
430 | 120 | """ | ||
431 | 121 | mock_get_default_env_name = mock.Mock(return_value=env_name) | ||
432 | 122 | path = 'quickstart.manage.utils.get_default_env_name' | ||
433 | 123 | return mock.patch(path, mock_get_default_env_name) | ||
434 | 124 | |||
435 | 125 | def call_setup(self, args, env_name=None): | ||
436 | 126 | """Call the setup function simulating the given args and env name. | ||
437 | 127 | |||
438 | 128 | Also ensure the program exits without errors. | ||
439 | 129 | """ | ||
440 | 130 | with mock.patch('sys.argv', ['juju-quickstart'] + args): | ||
441 | 131 | with mock.patch('sys.exit') as mock_exit: | ||
442 | 132 | with self.patch_get_default_env_name(env_name): | ||
443 | 133 | manage.setup() | ||
444 | 134 | mock_exit.assert_called_once_with(0) | ||
445 | 135 | |||
446 | 136 | def test_help(self): | ||
447 | 137 | # The program help message is properly formatted. | ||
448 | 138 | with mock.patch('sys.stdout') as mock_stdout: | ||
449 | 139 | self.call_setup(['--help']) | ||
450 | 140 | stdout_write = mock_stdout.write | ||
451 | 141 | self.assertTrue(stdout_write.called) | ||
452 | 142 | # Retrieve the output from the mock call. | ||
453 | 143 | output = stdout_write.call_args[0][0] | ||
454 | 144 | self.assertIn('usage: juju-quickstart', output) | ||
455 | 145 | self.assertIn(quickstart.__doc__, output) | ||
456 | 146 | self.assertIn('--environment', output) | ||
457 | 147 | # Without a default environment, the -e option has no default. | ||
458 | 148 | self.assertIn('the name of the Juju environment to use\n', output) | ||
459 | 149 | |||
460 | 150 | def test_help_with_default_environment(self): | ||
461 | 151 | # The program help message is properly formatted when a default Juju | ||
462 | 152 | # environment is found. | ||
463 | 153 | with mock.patch('sys.stdout') as mock_stdout: | ||
464 | 154 | self.call_setup(['--help'], env_name='hp') | ||
465 | 155 | stdout_write = mock_stdout.write | ||
466 | 156 | self.assertTrue(stdout_write.called) | ||
467 | 157 | # Retrieve the output from the mock call. | ||
468 | 158 | output = stdout_write.call_args[0][0] | ||
469 | 159 | self.assertIn('the name of the Juju environment to use (hp)\n', output) | ||
470 | 160 | |||
471 | 161 | def test_description(self): | ||
472 | 162 | # The program description is properly printed out as required by juju. | ||
473 | 163 | with mock.patch('__builtin__.print') as mock_print: | ||
474 | 164 | self.call_setup(['--description']) | ||
475 | 165 | mock_print.assert_called_once_with(manage.description) | ||
476 | 166 | |||
477 | 167 | def test_version(self): | ||
478 | 168 | # The program version is properly printed to stderr. | ||
479 | 169 | with mock.patch('sys.stderr') as mock_stderr: | ||
480 | 170 | self.call_setup(['--version']) | ||
481 | 171 | expected = 'juju-quickstart {}\n'.format(quickstart.get_version()) | ||
482 | 172 | mock_stderr.write.assert_called_once_with(expected) | ||
483 | 42 | 173 | ||
484 | === added file 'quickstart/tests/test_utils.py' | |||
485 | --- quickstart/tests/test_utils.py 1970-01-01 00:00:00 +0000 | |||
486 | +++ quickstart/tests/test_utils.py 2013-10-16 07:23:37 +0000 | |||
487 | @@ -0,0 +1,178 @@ | |||
488 | 1 | # This file is part of the Juju GUI, which lets users view and manage Juju | ||
489 | 2 | # environments within a graphical interface (https://launchpad.net/juju-gui). | ||
490 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
491 | 4 | # | ||
492 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
493 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
494 | 7 | # the Free Software Foundation. | ||
495 | 8 | # | ||
496 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
497 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
498 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
499 | 12 | # Affero General Public License for more details. | ||
500 | 13 | # | ||
501 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
502 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
503 | 16 | |||
504 | 17 | """Tests for the Juju Quickstart utility functions and classes.""" | ||
505 | 18 | |||
506 | 19 | import unittest | ||
507 | 20 | |||
508 | 21 | import mock | ||
509 | 22 | import yaml | ||
510 | 23 | |||
511 | 24 | from quickstart import utils | ||
512 | 25 | from quickstart.tests import helpers | ||
513 | 26 | |||
514 | 27 | |||
515 | 28 | class TestCall(unittest.TestCase): | ||
516 | 29 | |||
517 | 30 | def test_success(self): | ||
518 | 31 | # A zero exit code and the subprocess output are correctly returned. | ||
519 | 32 | retcode, output, error = utils.call('echo') | ||
520 | 33 | self.assertEqual(0, retcode) | ||
521 | 34 | self.assertEqual('\n', output) | ||
522 | 35 | self.assertEqual('', error) | ||
523 | 36 | |||
524 | 37 | def test_multiple_arguments(self): | ||
525 | 38 | # A zero exit code and the subprocess output are correctly returned | ||
526 | 39 | # when executing a command passing multiple arguments. | ||
527 | 40 | retcode, output, error = utils.call('echo', 'we are the borg!') | ||
528 | 41 | self.assertEqual(0, retcode) | ||
529 | 42 | self.assertEqual('we are the borg!\n', output) | ||
530 | 43 | self.assertEqual('', error) | ||
531 | 44 | |||
532 | 45 | def test_failure(self): | ||
533 | 46 | # An error code and the error are returned if the subprocess fails. | ||
534 | 47 | retcode, output, error = utils.call('ls', 'no-such-file') | ||
535 | 48 | self.assertNotEqual(0, retcode) | ||
536 | 49 | self.assertEqual('', output) | ||
537 | 50 | self.assertEqual( | ||
538 | 51 | 'ls: cannot access no-such-file: No such file or directory\n', | ||
539 | 52 | error) | ||
540 | 53 | |||
541 | 54 | def test_invalid_command(self): | ||
542 | 55 | # An error code and the error are returned if the subprocess fails to | ||
543 | 56 | # find the provided command in the PATH. | ||
544 | 57 | retcode, output, error = utils.call('no-such-command') | ||
545 | 58 | self.assertEqual(127, retcode) | ||
546 | 59 | self.assertEqual('', output) | ||
547 | 60 | self.assertEqual( | ||
548 | 61 | 'no-such-command: [Errno 2] No such file or directory', | ||
549 | 62 | error) | ||
550 | 63 | |||
551 | 64 | |||
552 | 65 | class TestGetDefaultEnvName(unittest.TestCase): | ||
553 | 66 | |||
554 | 67 | def patch_call(self, retcode, output='', error=''): | ||
555 | 68 | """Patch the quickstart.utils.call function.""" | ||
556 | 69 | mock_call = mock.Mock(return_value=(retcode, output, error)) | ||
557 | 70 | return mock.patch('quickstart.utils.call', mock_call) | ||
558 | 71 | |||
559 | 72 | def test_environment_variable(self): | ||
560 | 73 | # The environment name is successfully returned if JUJU_ENV is set. | ||
561 | 74 | with mock.patch('os.environ', {'JUJU_ENV': 'ec2'}): | ||
562 | 75 | env_name = utils.get_default_env_name() | ||
563 | 76 | self.assertEqual('ec2', env_name) | ||
564 | 77 | |||
565 | 78 | def test_empty_environment_variable(self): | ||
566 | 79 | # The environment name is not found if JUJU_ENV is empty. | ||
567 | 80 | with self.patch_call(1): | ||
568 | 81 | with mock.patch('os.environ', {'JUJU_ENV': ' '}): | ||
569 | 82 | env_name = utils.get_default_env_name() | ||
570 | 83 | self.assertIsNone(env_name) | ||
571 | 84 | |||
572 | 85 | def test_no_environment_variable(self): | ||
573 | 86 | # The environment name is not found if JUJU_ENV is not defined. | ||
574 | 87 | with self.patch_call(1): | ||
575 | 88 | with mock.patch('os.environ', {}): | ||
576 | 89 | env_name = utils.get_default_env_name() | ||
577 | 90 | self.assertIsNone(env_name) | ||
578 | 91 | |||
579 | 92 | def test_juju_switch(self): | ||
580 | 93 | # The environment name is successfully returned if previously set using | ||
581 | 94 | # the "juju switch" command. | ||
582 | 95 | output = 'Current environment: "hp"\n' | ||
583 | 96 | with self.patch_call(0, output=output) as mock_call: | ||
584 | 97 | with mock.patch('os.environ', {}): | ||
585 | 98 | env_name = utils.get_default_env_name() | ||
586 | 99 | self.assertEqual('hp', env_name) | ||
587 | 100 | mock_call.assert_called_once_with('juju', 'switch') | ||
588 | 101 | |||
589 | 102 | def test_juju_switch_failure(self): | ||
590 | 103 | # The environment name is not found if "juju switch" returns an error. | ||
591 | 104 | with self.patch_call(1) as mock_call: | ||
592 | 105 | with mock.patch('os.environ', {}): | ||
593 | 106 | env_name = utils.get_default_env_name() | ||
594 | 107 | self.assertIsNone(env_name) | ||
595 | 108 | mock_call.assert_called_once_with('juju', 'switch') | ||
596 | 109 | |||
597 | 110 | def test_juju_switch_nonsense(self): | ||
598 | 111 | # The environment name is not found if "juju switch" goes crazy. | ||
599 | 112 | with self.patch_call(0, output='Exterminate!') as mock_call: | ||
600 | 113 | with mock.patch('os.environ', {}): | ||
601 | 114 | env_name = utils.get_default_env_name() | ||
602 | 115 | self.assertIsNone(env_name) | ||
603 | 116 | mock_call.assert_called_once_with('juju', 'switch') | ||
604 | 117 | |||
605 | 118 | |||
606 | 119 | class TestParseEnvFile( | ||
607 | 120 | helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin, | ||
608 | 121 | unittest.TestCase): | ||
609 | 122 | |||
610 | 123 | def test_no_file(self): | ||
611 | 124 | # A ValueError is raised if the environments file is not found. | ||
612 | 125 | expected = ( | ||
613 | 126 | 'unable to open environments file: ' | ||
614 | 127 | "[Errno 2] No such file or directory: '/no/such/file.yaml'" | ||
615 | 128 | ) | ||
616 | 129 | with self.assert_value_error(expected): | ||
617 | 130 | utils.parse_env_file('/no/such/file.yaml', 'ec2') | ||
618 | 131 | |||
619 | 132 | def test_invalid_yaml(self): | ||
620 | 133 | # A ValueError is raised if the environments file is not a valid YAML. | ||
621 | 134 | env_file = self.make_env_file(':') | ||
622 | 135 | with self.assertRaises(ValueError) as context_manager: | ||
623 | 136 | utils.parse_env_file(env_file, 'ec2') | ||
624 | 137 | expected = 'unable to parse environments file {}'.format(env_file) | ||
625 | 138 | self.assertIn(expected, str(context_manager.exception)) | ||
626 | 139 | |||
627 | 140 | def test_invalid_yaml_contents(self): | ||
628 | 141 | # A ValueError is raised if the environments file is not well formed. | ||
629 | 142 | env_file = self.make_env_file('a-string') | ||
630 | 143 | expected = 'invalid YAML contents in {}: a-string'.format(env_file) | ||
631 | 144 | with self.assert_value_error(expected): | ||
632 | 145 | utils.parse_env_file(env_file, 'ec2') | ||
633 | 146 | |||
634 | 147 | def test_no_env(self): | ||
635 | 148 | # A ValueError is raised if the environment is not found in the YAML. | ||
636 | 149 | contents = yaml.safe_dump({'environments': {'local': {}}}) | ||
637 | 150 | env_file = self.make_env_file(contents) | ||
638 | 151 | expected = 'environment ec2 not found in {}'.format(env_file) | ||
639 | 152 | with self.assert_value_error(expected): | ||
640 | 153 | utils.parse_env_file(env_file, 'ec2') | ||
641 | 154 | |||
642 | 155 | def test_no_provider_type(self): | ||
643 | 156 | # A ValueError is raised if the provider type is not included in the | ||
644 | 157 | # environment info. | ||
645 | 158 | contents = yaml.safe_dump({'environments': {'aws': {}}}) | ||
646 | 159 | env_file = self.make_env_file(contents) | ||
647 | 160 | expected = 'aws provider type not found in {}'.format(env_file) | ||
648 | 161 | with self.assert_value_error(expected): | ||
649 | 162 | utils.parse_env_file(env_file, 'aws') | ||
650 | 163 | |||
651 | 164 | def test_no_admin_secret(self): | ||
652 | 165 | # A ValueError is raised if the admin secret is not included in the | ||
653 | 166 | # environment info. | ||
654 | 167 | contents = yaml.safe_dump({'environments': {'aws': {'type': 'ec2'}}}) | ||
655 | 168 | env_file = self.make_env_file(contents) | ||
656 | 169 | expected = 'aws admin secret not found in {}'.format(env_file) | ||
657 | 170 | with self.assert_value_error(expected): | ||
658 | 171 | utils.parse_env_file(env_file, 'aws') | ||
659 | 172 | |||
660 | 173 | def test_success(self): | ||
661 | 174 | # The environment provider type and admin secret are returned. | ||
662 | 175 | env_file = self.make_env_file() | ||
663 | 176 | env_type, admin_secret = utils.parse_env_file(env_file, 'aws') | ||
664 | 177 | self.assertEqual('ec2', env_type) | ||
665 | 178 | self.assertEqual('Secret!', admin_secret) | ||
666 | 0 | 179 | ||
667 | === added file 'quickstart/utils.py' | |||
668 | --- quickstart/utils.py 1970-01-01 00:00:00 +0000 | |||
669 | +++ quickstart/utils.py 2013-10-16 07:23:37 +0000 | |||
670 | @@ -0,0 +1,110 @@ | |||
671 | 1 | # This file is part of the Juju GUI, which lets users view and manage Juju | ||
672 | 2 | # environments within a graphical interface (https://launchpad.net/juju-gui). | ||
673 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
674 | 4 | # | ||
675 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
676 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
677 | 7 | # the Free Software Foundation. | ||
678 | 8 | # | ||
679 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
680 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
681 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
682 | 12 | # Affero General Public License for more details. | ||
683 | 13 | # | ||
684 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
685 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
686 | 16 | |||
687 | 17 | """Juju Quickstart utility functions and classes.""" | ||
688 | 18 | |||
689 | 19 | import re | ||
690 | 20 | import os | ||
691 | 21 | import subprocess | ||
692 | 22 | |||
693 | 23 | import yaml | ||
694 | 24 | |||
695 | 25 | # Compile the regular expression used to parse the "juju switch" output. | ||
696 | 26 | _juju_switch_expression = re.compile(r'Current environment: "([\w-]+)"\n') | ||
697 | 27 | |||
698 | 28 | |||
699 | 29 | def call(*args): | ||
700 | 30 | """Call a subprocess passing the given arguments. | ||
701 | 31 | |||
702 | 32 | Take the subcommand and its parameters as args. | ||
703 | 33 | |||
704 | 34 | Return a tuple containing the subprocess return code, output and error. | ||
705 | 35 | """ | ||
706 | 36 | pipe = subprocess.PIPE | ||
707 | 37 | try: | ||
708 | 38 | process = subprocess.Popen(args, stdout=pipe, stderr=pipe) | ||
709 | 39 | except OSError as err: | ||
710 | 40 | # A return code 127 is returned by the shell when the command is not | ||
711 | 41 | # found in the PATH. | ||
712 | 42 | return 127, '', '{}: {}'.format(args[0], err) | ||
713 | 43 | output, error = process.communicate() | ||
714 | 44 | return process.poll(), output, error | ||
715 | 45 | |||
716 | 46 | |||
717 | 47 | def get_default_env_name(): | ||
718 | 48 | """Return the current Juju environment name. | ||
719 | 49 | |||
720 | 50 | The environment name can be set either by setting the JUJU_ENV environment | ||
721 | 51 | variable or by using "juju switch". The former overrides the latter. | ||
722 | 52 | |||
723 | 53 | Return None if a default environment is not found. | ||
724 | 54 | """ | ||
725 | 55 | env_name = os.getenv('JUJU_ENV', '').strip() | ||
726 | 56 | if env_name: | ||
727 | 57 | return env_name | ||
728 | 58 | retcode, output, _ = call('juju', 'switch') | ||
729 | 59 | if retcode: | ||
730 | 60 | return None | ||
731 | 61 | match = _juju_switch_expression.match(output) | ||
732 | 62 | if match is not None: | ||
733 | 63 | return match.groups()[0] | ||
734 | 64 | |||
735 | 65 | |||
736 | 66 | def parse_env_file(env_file, env_name): | ||
737 | 67 | """Parse the provided Juju environment.yaml file. | ||
738 | 68 | |||
739 | 69 | Return a tuple containing the provider type and the admin secret associated | ||
740 | 70 | with the given environment name. | ||
741 | 71 | |||
742 | 72 | Raise a ValueError if: | ||
743 | 73 | - the environment file is not found; | ||
744 | 74 | - the environment file contents are not parsable by YAML; | ||
745 | 75 | - the YAML contents are not properly structured; | ||
746 | 76 | - the environment named envname is not found; | ||
747 | 77 | - the environment does not include the "type" field; | ||
748 | 78 | - the environment does not include the "admin_secret" field. | ||
749 | 79 | """ | ||
750 | 80 | # Load the Juju environments file. | ||
751 | 81 | try: | ||
752 | 82 | environments_file = open(env_file) | ||
753 | 83 | except IOError as err: | ||
754 | 84 | msg = 'unable to open environments file: {}'.format(err) | ||
755 | 85 | raise ValueError(msg) | ||
756 | 86 | # Parse the Juju environments file. | ||
757 | 87 | try: | ||
758 | 88 | environments = yaml.safe_load(environments_file) | ||
759 | 89 | except Exception as err: | ||
760 | 90 | msg = 'unable to parse environments file {}: {}'.format(env_file, err) | ||
761 | 91 | raise ValueError(msg) | ||
762 | 92 | # Retrieve the information about the current environment. | ||
763 | 93 | try: | ||
764 | 94 | environment = environments.get('environments', {}).get(env_name) | ||
765 | 95 | except AttributeError as err: | ||
766 | 96 | msg = 'invalid YAML contents in {}: {}'.format(env_file, environments) | ||
767 | 97 | raise ValueError(msg) | ||
768 | 98 | if environment is None: | ||
769 | 99 | msg = 'environment {} not found in {}'.format(env_name, env_file) | ||
770 | 100 | raise ValueError(msg) | ||
771 | 101 | # Retrieve the provider type and the admin secret. | ||
772 | 102 | env_type = environment.get('type') | ||
773 | 103 | if env_type is None: | ||
774 | 104 | msg = '{} provider type not found in {}'.format(env_name, env_file) | ||
775 | 105 | raise ValueError(msg) | ||
776 | 106 | admin_secret = environment.get('admin-secret') | ||
777 | 107 | if admin_secret is None: | ||
778 | 108 | msg = '{} admin secret not found in {}'.format(env_name, env_file) | ||
779 | 109 | raise ValueError(msg) | ||
780 | 110 | return env_type, admin_secret |
Reviewers: mp+191234_ code.launchpad. net,
Message:
Please take a look.
Description:
Parse and validate the environment file/options.
Check if the given environment name exists,
ensure it does not use the local provider,
ensure it includes an admin-secret,
retrieve the admin-secret.
Tests: make check
https:/ /code.launchpad .net/~frankban/ juju-gui/ quickstart- bootstrap/ +merge/ 191234
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/14669047/
Affected files (+633, -17 lines): manage. py tests/helpers. py tests/test_ app.py tests/test_ manage. py tests/test_ utils.py
M Makefile
A [revision details]
M juju-quickstart
A quickstart/app.py
M quickstart/
A quickstart/
A quickstart/
M quickstart/
A quickstart/
A quickstart/utils.py