Merge lp:~frankban/juju-quickstart/env-management-1 into lp:juju-quickstart
- env-management-1
- Merge into trunk
Proposed by
Francesco Banconi
Status: | Merged |
---|---|
Merged at revision: | 25 |
Proposed branch: | lp:~frankban/juju-quickstart/env-management-1 |
Merge into: | lp:juju-quickstart |
Diff against target: |
600 lines (+269/-204) 8 files modified
quickstart/manage.py (+6/-3) quickstart/models/__init__.py (+17/-0) quickstart/models/envs.py (+104/-0) quickstart/tests/models/test_charms.py (+1/-1) quickstart/tests/models/test_envs.py (+136/-0) quickstart/tests/test_manage.py (+3/-4) quickstart/tests/test_utils.py (+0/-111) quickstart/utils.py (+2/-85) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/env-management-1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email:
|
Commit message
Description of the change
Envs management: code reorganization.
This branch includes code reorg as
a preparation for the upcoming envs
management work.
It's just about mechanically moving code
to the new models package, except for a
minor fix to test_init_
test_manage.py.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gary Poster (gary) wrote : | # |
LGTM. Thank you!
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Envs management: code reorganization.
This branch includes code reorg as
a preparation for the upcoming envs
management work.
It's just about mechanically moving code
to the new models package, except for a
minor fix to test_init_
test_manage.py.
R=gary.poster
CC=
https:/
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'quickstart/manage.py' | |||
2 | --- quickstart/manage.py 2013-12-02 21:36:04 +0000 | |||
3 | +++ quickstart/manage.py 2013-12-04 13:19:54 +0000 | |||
4 | @@ -25,10 +25,13 @@ | |||
5 | 25 | import quickstart | 25 | import quickstart |
6 | 26 | from quickstart import ( | 26 | from quickstart import ( |
7 | 27 | app, | 27 | app, |
8 | 28 | charms, | ||
9 | 29 | settings, | 28 | settings, |
10 | 30 | utils, | 29 | utils, |
11 | 31 | ) | 30 | ) |
12 | 31 | from quickstart.models import ( | ||
13 | 32 | charms, | ||
14 | 33 | envs, | ||
15 | 34 | ) | ||
16 | 32 | 35 | ||
17 | 33 | 36 | ||
18 | 34 | version = quickstart.get_version() | 37 | version = quickstart.get_version() |
19 | @@ -144,7 +147,7 @@ | |||
20 | 144 | ) | 147 | ) |
21 | 145 | # Validate the environment file. | 148 | # Validate the environment file. |
22 | 146 | try: | 149 | try: |
24 | 147 | env_type, admin_secret = utils.parse_env_file(env_file, env_name) | 150 | env_type, admin_secret = envs.parse_env_file(env_file, env_name) |
25 | 148 | except ValueError as err: | 151 | except ValueError as err: |
26 | 149 | return parser.error(str(err)) | 152 | return parser.error(str(err)) |
27 | 150 | # Update the options namespace with the new values. | 153 | # Update the options namespace with the new values. |
28 | @@ -193,7 +196,7 @@ | |||
29 | 193 | 196 | ||
30 | 194 | Exit with an error if the provided arguments are not valid. | 197 | Exit with an error if the provided arguments are not valid. |
31 | 195 | """ | 198 | """ |
33 | 196 | default_env_name = utils.get_default_env_name() | 199 | default_env_name = envs.get_default_env_name() |
34 | 197 | # Define the help message for the --environment option. | 200 | # Define the help message for the --environment option. |
35 | 198 | env_help = 'The name of the Juju environment to use' | 201 | env_help = 'The name of the Juju environment to use' |
36 | 199 | # XXX 2013-12-02 makyo: | 202 | # XXX 2013-12-02 makyo: |
37 | 200 | 203 | ||
38 | === added directory 'quickstart/models' | |||
39 | === added file 'quickstart/models/__init__.py' | |||
40 | --- quickstart/models/__init__.py 1970-01-01 00:00:00 +0000 | |||
41 | +++ quickstart/models/__init__.py 2013-12-04 13:19:54 +0000 | |||
42 | @@ -0,0 +1,17 @@ | |||
43 | 1 | # This file is part of the Juju Quickstart Plugin, which lets users set up a | ||
44 | 2 | # Juju environment in very few steps (https://launchpad.net/juju-quickstart). | ||
45 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
46 | 4 | # | ||
47 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
48 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
49 | 7 | # the Free Software Foundation. | ||
50 | 8 | # | ||
51 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
52 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
53 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
54 | 12 | # Affero General Public License for more details. | ||
55 | 13 | # | ||
56 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
57 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
58 | 16 | |||
59 | 17 | """Juju Quickstart models.""" | ||
60 | 0 | 18 | ||
61 | === renamed file 'quickstart/charms.py' => 'quickstart/models/charms.py' | |||
62 | === added file 'quickstart/models/envs.py' | |||
63 | --- quickstart/models/envs.py 1970-01-01 00:00:00 +0000 | |||
64 | +++ quickstart/models/envs.py 2013-12-04 13:19:54 +0000 | |||
65 | @@ -0,0 +1,104 @@ | |||
66 | 1 | # This file is part of the Juju Quickstart Plugin, which lets users set up a | ||
67 | 2 | # Juju environment in very few steps (https://launchpad.net/juju-quickstart). | ||
68 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
69 | 4 | # | ||
70 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
71 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
72 | 7 | # the Free Software Foundation. | ||
73 | 8 | # | ||
74 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
75 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
76 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
77 | 12 | # Affero General Public License for more details. | ||
78 | 13 | # | ||
79 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
80 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
81 | 16 | |||
82 | 17 | """Juju Quickstart environments management.""" | ||
83 | 18 | |||
84 | 19 | import os | ||
85 | 20 | import re | ||
86 | 21 | |||
87 | 22 | import yaml | ||
88 | 23 | |||
89 | 24 | from quickstart import utils | ||
90 | 25 | |||
91 | 26 | |||
92 | 27 | # Compile the regular expression used to parse the "juju switch" output. | ||
93 | 28 | _juju_switch_expression = re.compile(r'Current environment: "([\w-]+)"\n') | ||
94 | 29 | |||
95 | 30 | |||
96 | 31 | def get_default_env_name(): | ||
97 | 32 | """Return the current Juju environment name. | ||
98 | 33 | |||
99 | 34 | The environment name can be set either by | ||
100 | 35 | - setting the JUJU_ENV environment variable; | ||
101 | 36 | - using "juju switch my-env-name"; | ||
102 | 37 | - setting the default environment in the environments.yaml file. | ||
103 | 38 | The former overrides the latter. | ||
104 | 39 | |||
105 | 40 | Return None if a default environment is not found. | ||
106 | 41 | """ | ||
107 | 42 | env_name = os.getenv('JUJU_ENV', '').strip() | ||
108 | 43 | if env_name: | ||
109 | 44 | return env_name | ||
110 | 45 | # XXX 2013-10-16 frankban bug=1193244: | ||
111 | 46 | # Support the new behavior of juju-switch, currently under development: | ||
112 | 47 | # the command will just output the environment name, or exit with an | ||
113 | 48 | # error if no default environment is configured. | ||
114 | 49 | # The "juju switch" command parses ~/.juju/current-environment file. If the | ||
115 | 50 | # environment name is not found there, then it tries to retrieve the name | ||
116 | 51 | # from the "default" section of the ~/.juju/environments.yaml file. | ||
117 | 52 | retcode, output, _ = utils.call('juju', 'switch') | ||
118 | 53 | if retcode: | ||
119 | 54 | return None | ||
120 | 55 | match = _juju_switch_expression.match(output) | ||
121 | 56 | if match is not None: | ||
122 | 57 | return match.groups()[0] | ||
123 | 58 | |||
124 | 59 | |||
125 | 60 | def parse_env_file(env_file, env_name): | ||
126 | 61 | """Parse the provided Juju environments.yaml file. | ||
127 | 62 | |||
128 | 63 | Return a tuple containing the provider type and the admin secret associated | ||
129 | 64 | with the given environment name. | ||
130 | 65 | |||
131 | 66 | Raise a ValueError if: | ||
132 | 67 | - the environment file is not found; | ||
133 | 68 | - the environment file contents are not parsable by YAML; | ||
134 | 69 | - the YAML contents are not properly structured; | ||
135 | 70 | - the environment named envname is not found; | ||
136 | 71 | - the environment does not include the "type" field; | ||
137 | 72 | - the environment does not include the "admin_secret" field. | ||
138 | 73 | """ | ||
139 | 74 | # Load the Juju environments file. | ||
140 | 75 | try: | ||
141 | 76 | environments_file = open(env_file) | ||
142 | 77 | except IOError as err: | ||
143 | 78 | msg = 'unable to open environments file: {}'.format(err) | ||
144 | 79 | raise ValueError(msg) | ||
145 | 80 | # Parse the Juju environments file. | ||
146 | 81 | try: | ||
147 | 82 | environments = yaml.safe_load(environments_file) | ||
148 | 83 | except Exception as err: | ||
149 | 84 | msg = 'unable to parse environments file {}: {}'.format(env_file, err) | ||
150 | 85 | raise ValueError(msg) | ||
151 | 86 | # Retrieve the information about the current environment. | ||
152 | 87 | try: | ||
153 | 88 | environment = environments.get('environments', {}).get(env_name) | ||
154 | 89 | except AttributeError as err: | ||
155 | 90 | msg = 'invalid YAML contents in {}: {}'.format(env_file, environments) | ||
156 | 91 | raise ValueError(msg) | ||
157 | 92 | if environment is None: | ||
158 | 93 | msg = 'environment {} not found in {}'.format(env_name, env_file) | ||
159 | 94 | raise ValueError(msg) | ||
160 | 95 | # Retrieve the provider type and the admin secret. | ||
161 | 96 | env_type = environment.get('type') | ||
162 | 97 | if env_type is None: | ||
163 | 98 | msg = '{} provider type not found in {}'.format(env_name, env_file) | ||
164 | 99 | raise ValueError(msg) | ||
165 | 100 | admin_secret = environment.get('admin-secret') | ||
166 | 101 | if admin_secret is None: | ||
167 | 102 | msg = '{} admin secret not found in {}'.format(env_name, env_file) | ||
168 | 103 | raise ValueError(msg) | ||
169 | 104 | return env_type, admin_secret | ||
170 | 0 | 105 | ||
171 | === added directory 'quickstart/tests/models' | |||
172 | === added file 'quickstart/tests/models/__init__.py' | |||
173 | === renamed file 'quickstart/tests/test_charms.py' => 'quickstart/tests/models/test_charms.py' | |||
174 | --- quickstart/tests/test_charms.py 2013-11-22 08:30:42 +0000 | |||
175 | +++ quickstart/tests/models/test_charms.py 2013-12-04 13:19:54 +0000 | |||
176 | @@ -18,7 +18,7 @@ | |||
177 | 18 | 18 | ||
178 | 19 | import unittest | 19 | import unittest |
179 | 20 | 20 | ||
181 | 21 | from quickstart import charms | 21 | from quickstart.models import charms |
182 | 22 | from quickstart.tests import helpers | 22 | from quickstart.tests import helpers |
183 | 23 | 23 | ||
184 | 24 | 24 | ||
185 | 25 | 25 | ||
186 | === added file 'quickstart/tests/models/test_envs.py' | |||
187 | --- quickstart/tests/models/test_envs.py 1970-01-01 00:00:00 +0000 | |||
188 | +++ quickstart/tests/models/test_envs.py 2013-12-04 13:19:54 +0000 | |||
189 | @@ -0,0 +1,136 @@ | |||
190 | 1 | # This file is part of the Juju Quickstart Plugin, which lets users set up a | ||
191 | 2 | # Juju environment in very few steps (https://launchpad.net/juju-quickstart). | ||
192 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
193 | 4 | # | ||
194 | 5 | # This program is free software: you can redistribute it and/or modify it under | ||
195 | 6 | # the terms of the GNU Affero General Public License version 3, as published by | ||
196 | 7 | # the Free Software Foundation. | ||
197 | 8 | # | ||
198 | 9 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
199 | 10 | # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | ||
200 | 11 | # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
201 | 12 | # Affero General Public License for more details. | ||
202 | 13 | # | ||
203 | 14 | # You should have received a copy of the GNU Affero General Public License | ||
204 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
205 | 16 | |||
206 | 17 | """Tests for the Juju Quickstart environments management.""" | ||
207 | 18 | |||
208 | 19 | import unittest | ||
209 | 20 | |||
210 | 21 | import mock | ||
211 | 22 | import yaml | ||
212 | 23 | |||
213 | 24 | from quickstart.models import envs | ||
214 | 25 | from quickstart.tests import helpers | ||
215 | 26 | |||
216 | 27 | |||
217 | 28 | class TestGetDefaultEnvName(helpers.CallTestsMixin, unittest.TestCase): | ||
218 | 29 | |||
219 | 30 | def test_environment_variable(self): | ||
220 | 31 | # The environment name is successfully returned if JUJU_ENV is set. | ||
221 | 32 | with mock.patch('os.environ', {'JUJU_ENV': 'ec2'}): | ||
222 | 33 | env_name = envs.get_default_env_name() | ||
223 | 34 | self.assertEqual('ec2', env_name) | ||
224 | 35 | |||
225 | 36 | def test_empty_environment_variable(self): | ||
226 | 37 | # The environment name is not found if JUJU_ENV is empty. | ||
227 | 38 | with self.patch_call(1): | ||
228 | 39 | with mock.patch('os.environ', {'JUJU_ENV': ' '}): | ||
229 | 40 | env_name = envs.get_default_env_name() | ||
230 | 41 | self.assertIsNone(env_name) | ||
231 | 42 | |||
232 | 43 | def test_no_environment_variable(self): | ||
233 | 44 | # The environment name is not found if JUJU_ENV is not defined. | ||
234 | 45 | with self.patch_call(1): | ||
235 | 46 | with mock.patch('os.environ', {}): | ||
236 | 47 | env_name = envs.get_default_env_name() | ||
237 | 48 | self.assertIsNone(env_name) | ||
238 | 49 | |||
239 | 50 | def test_juju_switch(self): | ||
240 | 51 | # The environment name is successfully returned if previously set using | ||
241 | 52 | # the "juju switch" command. | ||
242 | 53 | output = 'Current environment: "hp"\n' | ||
243 | 54 | with self.patch_call(0, output=output) as mock_call: | ||
244 | 55 | with mock.patch('os.environ', {}): | ||
245 | 56 | env_name = envs.get_default_env_name() | ||
246 | 57 | self.assertEqual('hp', env_name) | ||
247 | 58 | mock_call.assert_called_once_with('juju', 'switch') | ||
248 | 59 | |||
249 | 60 | def test_juju_switch_failure(self): | ||
250 | 61 | # The environment name is not found if "juju switch" returns an error. | ||
251 | 62 | with self.patch_call(1) as mock_call: | ||
252 | 63 | with mock.patch('os.environ', {}): | ||
253 | 64 | env_name = envs.get_default_env_name() | ||
254 | 65 | self.assertIsNone(env_name) | ||
255 | 66 | mock_call.assert_called_once_with('juju', 'switch') | ||
256 | 67 | |||
257 | 68 | def test_juju_switch_nonsense(self): | ||
258 | 69 | # The environment name is not found if "juju switch" goes crazy. | ||
259 | 70 | with self.patch_call(0, output='Exterminate!') as mock_call: | ||
260 | 71 | with mock.patch('os.environ', {}): | ||
261 | 72 | env_name = envs.get_default_env_name() | ||
262 | 73 | self.assertIsNone(env_name) | ||
263 | 74 | mock_call.assert_called_once_with('juju', 'switch') | ||
264 | 75 | |||
265 | 76 | |||
266 | 77 | class TestParseEnvFile( | ||
267 | 78 | helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin, | ||
268 | 79 | unittest.TestCase): | ||
269 | 80 | |||
270 | 81 | def test_no_file(self): | ||
271 | 82 | # A ValueError is raised if the environments file is not found. | ||
272 | 83 | expected = ( | ||
273 | 84 | 'unable to open environments file: ' | ||
274 | 85 | "[Errno 2] No such file or directory: '/no/such/file.yaml'" | ||
275 | 86 | ) | ||
276 | 87 | with self.assert_value_error(expected): | ||
277 | 88 | envs.parse_env_file('/no/such/file.yaml', 'ec2') | ||
278 | 89 | |||
279 | 90 | def test_invalid_yaml(self): | ||
280 | 91 | # A ValueError is raised if the environments file is not a valid YAML. | ||
281 | 92 | env_file = self.make_env_file(':') | ||
282 | 93 | with self.assertRaises(ValueError) as context_manager: | ||
283 | 94 | envs.parse_env_file(env_file, 'ec2') | ||
284 | 95 | expected = 'unable to parse environments file {}'.format(env_file) | ||
285 | 96 | self.assertIn(expected, str(context_manager.exception)) | ||
286 | 97 | |||
287 | 98 | def test_invalid_yaml_contents(self): | ||
288 | 99 | # A ValueError is raised if the environments file is not well formed. | ||
289 | 100 | env_file = self.make_env_file('a-string') | ||
290 | 101 | expected = 'invalid YAML contents in {}: a-string'.format(env_file) | ||
291 | 102 | with self.assert_value_error(expected): | ||
292 | 103 | envs.parse_env_file(env_file, 'ec2') | ||
293 | 104 | |||
294 | 105 | def test_no_env(self): | ||
295 | 106 | # A ValueError is raised if the environment is not found in the YAML. | ||
296 | 107 | contents = yaml.safe_dump({'environments': {'local': {}}}) | ||
297 | 108 | env_file = self.make_env_file(contents) | ||
298 | 109 | expected = 'environment ec2 not found in {}'.format(env_file) | ||
299 | 110 | with self.assert_value_error(expected): | ||
300 | 111 | envs.parse_env_file(env_file, 'ec2') | ||
301 | 112 | |||
302 | 113 | def test_no_provider_type(self): | ||
303 | 114 | # A ValueError is raised if the provider type is not included in the | ||
304 | 115 | # environment info. | ||
305 | 116 | contents = yaml.safe_dump({'environments': {'aws': {}}}) | ||
306 | 117 | env_file = self.make_env_file(contents) | ||
307 | 118 | expected = 'aws provider type not found in {}'.format(env_file) | ||
308 | 119 | with self.assert_value_error(expected): | ||
309 | 120 | envs.parse_env_file(env_file, 'aws') | ||
310 | 121 | |||
311 | 122 | def test_no_admin_secret(self): | ||
312 | 123 | # A ValueError is raised if the admin secret is not included in the | ||
313 | 124 | # environment info. | ||
314 | 125 | contents = yaml.safe_dump({'environments': {'aws': {'type': 'ec2'}}}) | ||
315 | 126 | env_file = self.make_env_file(contents) | ||
316 | 127 | expected = 'aws admin secret not found in {}'.format(env_file) | ||
317 | 128 | with self.assert_value_error(expected): | ||
318 | 129 | envs.parse_env_file(env_file, 'aws') | ||
319 | 130 | |||
320 | 131 | def test_success(self): | ||
321 | 132 | # The environment provider type and admin secret are returned. | ||
322 | 133 | env_file = self.make_env_file() | ||
323 | 134 | env_type, admin_secret = envs.parse_env_file(env_file, 'aws') | ||
324 | 135 | self.assertEqual('ec2', env_type) | ||
325 | 136 | self.assertEqual('Secret!', admin_secret) | ||
326 | 0 | 137 | ||
327 | === modified file 'quickstart/tests/test_manage.py' | |||
328 | --- quickstart/tests/test_manage.py 2013-12-02 21:36:04 +0000 | |||
329 | +++ quickstart/tests/test_manage.py 2013-12-04 13:19:54 +0000 | |||
330 | @@ -317,7 +317,7 @@ | |||
331 | 317 | and it is also possible to simulate an arbitrary environment name. | 317 | and it is also possible to simulate an arbitrary environment name. |
332 | 318 | """ | 318 | """ |
333 | 319 | mock_get_default_env_name = mock.Mock(return_value=env_name) | 319 | mock_get_default_env_name = mock.Mock(return_value=env_name) |
335 | 320 | path = 'quickstart.manage.utils.get_default_env_name' | 320 | path = 'quickstart.manage.envs.get_default_env_name' |
336 | 321 | return mock.patch(path, mock_get_default_env_name) | 321 | return mock.patch(path, mock_get_default_env_name) |
337 | 322 | 322 | ||
338 | 323 | def call_setup(self, args, env_name='ec2', exit_called=True): | 323 | def call_setup(self, args, env_name='ec2', exit_called=True): |
339 | @@ -358,12 +358,11 @@ | |||
340 | 358 | output = stdout_write.call_args[0][0] | 358 | output = stdout_write.call_args[0][0] |
341 | 359 | self.assertIn('The name of the Juju environment to use (hp)\n', output) | 359 | self.assertIn('The name of the Juju environment to use (hp)\n', output) |
342 | 360 | 360 | ||
344 | 361 | def test_help_with_init_environment(self): | 361 | def test_init_environment(self): |
345 | 362 | # The program calls ensure_environments if env_name is None. | 362 | # The program calls ensure_environments if env_name is None. |
346 | 363 | with mock.patch('quickstart.app.ensure_environments', | 363 | with mock.patch('quickstart.app.ensure_environments', |
347 | 364 | return_value='local') as ensure_environments: | 364 | return_value='local') as ensure_environments: |
350 | 365 | self.call_setup(['/path/to/bundle.file'], env_name=None, | 365 | self.call_setup([], env_name=None, exit_called=False) |
349 | 366 | exit_called=False) | ||
351 | 367 | self.assertTrue(ensure_environments.called) | 366 | self.assertTrue(ensure_environments.called) |
352 | 368 | 367 | ||
353 | 369 | def test_description(self): | 368 | def test_description(self): |
354 | 370 | 369 | ||
355 | === modified file 'quickstart/tests/test_utils.py' | |||
356 | --- quickstart/tests/test_utils.py 2013-11-26 09:45:17 +0000 | |||
357 | +++ quickstart/tests/test_utils.py 2013-12-04 13:19:54 +0000 | |||
358 | @@ -235,55 +235,6 @@ | |||
359 | 235 | 'unable to find the charm URL', str(context_manager.exception)) | 235 | 'unable to find the charm URL', str(context_manager.exception)) |
360 | 236 | 236 | ||
361 | 237 | 237 | ||
362 | 238 | class TestGetDefaultEnvName(helpers.CallTestsMixin, unittest.TestCase): | ||
363 | 239 | |||
364 | 240 | def test_environment_variable(self): | ||
365 | 241 | # The environment name is successfully returned if JUJU_ENV is set. | ||
366 | 242 | with mock.patch('os.environ', {'JUJU_ENV': 'ec2'}): | ||
367 | 243 | env_name = utils.get_default_env_name() | ||
368 | 244 | self.assertEqual('ec2', env_name) | ||
369 | 245 | |||
370 | 246 | def test_empty_environment_variable(self): | ||
371 | 247 | # The environment name is not found if JUJU_ENV is empty. | ||
372 | 248 | with self.patch_call(1): | ||
373 | 249 | with mock.patch('os.environ', {'JUJU_ENV': ' '}): | ||
374 | 250 | env_name = utils.get_default_env_name() | ||
375 | 251 | self.assertIsNone(env_name) | ||
376 | 252 | |||
377 | 253 | def test_no_environment_variable(self): | ||
378 | 254 | # The environment name is not found if JUJU_ENV is not defined. | ||
379 | 255 | with self.patch_call(1): | ||
380 | 256 | with mock.patch('os.environ', {}): | ||
381 | 257 | env_name = utils.get_default_env_name() | ||
382 | 258 | self.assertIsNone(env_name) | ||
383 | 259 | |||
384 | 260 | def test_juju_switch(self): | ||
385 | 261 | # The environment name is successfully returned if previously set using | ||
386 | 262 | # the "juju switch" command. | ||
387 | 263 | output = 'Current environment: "hp"\n' | ||
388 | 264 | with self.patch_call(0, output=output) as mock_call: | ||
389 | 265 | with mock.patch('os.environ', {}): | ||
390 | 266 | env_name = utils.get_default_env_name() | ||
391 | 267 | self.assertEqual('hp', env_name) | ||
392 | 268 | mock_call.assert_called_once_with('juju', 'switch') | ||
393 | 269 | |||
394 | 270 | def test_juju_switch_failure(self): | ||
395 | 271 | # The environment name is not found if "juju switch" returns an error. | ||
396 | 272 | with self.patch_call(1) as mock_call: | ||
397 | 273 | with mock.patch('os.environ', {}): | ||
398 | 274 | env_name = utils.get_default_env_name() | ||
399 | 275 | self.assertIsNone(env_name) | ||
400 | 276 | mock_call.assert_called_once_with('juju', 'switch') | ||
401 | 277 | |||
402 | 278 | def test_juju_switch_nonsense(self): | ||
403 | 279 | # The environment name is not found if "juju switch" goes crazy. | ||
404 | 280 | with self.patch_call(0, output='Exterminate!') as mock_call: | ||
405 | 281 | with mock.patch('os.environ', {}): | ||
406 | 282 | env_name = utils.get_default_env_name() | ||
407 | 283 | self.assertIsNone(env_name) | ||
408 | 284 | mock_call.assert_called_once_with('juju', 'switch') | ||
409 | 285 | |||
410 | 286 | |||
411 | 287 | class TestGetServiceInfo(helpers.WatcherDataTestsMixin, unittest.TestCase): | 238 | class TestGetServiceInfo(helpers.WatcherDataTestsMixin, unittest.TestCase): |
412 | 288 | 239 | ||
413 | 289 | def test_service_and_unit(self): | 240 | def test_service_and_unit(self): |
414 | @@ -487,68 +438,6 @@ | |||
415 | 487 | self.assert_bundle('mybundle', ['mysql', 'wordpress'], contents) | 438 | self.assert_bundle('mybundle', ['mysql', 'wordpress'], contents) |
416 | 488 | 439 | ||
417 | 489 | 440 | ||
418 | 490 | class TestParseEnvFile( | ||
419 | 491 | helpers.EnvFileTestsMixin, helpers.ValueErrorTestsMixin, | ||
420 | 492 | unittest.TestCase): | ||
421 | 493 | |||
422 | 494 | def test_no_file(self): | ||
423 | 495 | # A ValueError is raised if the environments file is not found. | ||
424 | 496 | expected = ( | ||
425 | 497 | 'unable to open environments file: ' | ||
426 | 498 | "[Errno 2] No such file or directory: '/no/such/file.yaml'" | ||
427 | 499 | ) | ||
428 | 500 | with self.assert_value_error(expected): | ||
429 | 501 | utils.parse_env_file('/no/such/file.yaml', 'ec2') | ||
430 | 502 | |||
431 | 503 | def test_invalid_yaml(self): | ||
432 | 504 | # A ValueError is raised if the environments file is not a valid YAML. | ||
433 | 505 | env_file = self.make_env_file(':') | ||
434 | 506 | with self.assertRaises(ValueError) as context_manager: | ||
435 | 507 | utils.parse_env_file(env_file, 'ec2') | ||
436 | 508 | expected = 'unable to parse environments file {}'.format(env_file) | ||
437 | 509 | self.assertIn(expected, str(context_manager.exception)) | ||
438 | 510 | |||
439 | 511 | def test_invalid_yaml_contents(self): | ||
440 | 512 | # A ValueError is raised if the environments file is not well formed. | ||
441 | 513 | env_file = self.make_env_file('a-string') | ||
442 | 514 | expected = 'invalid YAML contents in {}: a-string'.format(env_file) | ||
443 | 515 | with self.assert_value_error(expected): | ||
444 | 516 | utils.parse_env_file(env_file, 'ec2') | ||
445 | 517 | |||
446 | 518 | def test_no_env(self): | ||
447 | 519 | # A ValueError is raised if the environment is not found in the YAML. | ||
448 | 520 | contents = yaml.safe_dump({'environments': {'local': {}}}) | ||
449 | 521 | env_file = self.make_env_file(contents) | ||
450 | 522 | expected = 'environment ec2 not found in {}'.format(env_file) | ||
451 | 523 | with self.assert_value_error(expected): | ||
452 | 524 | utils.parse_env_file(env_file, 'ec2') | ||
453 | 525 | |||
454 | 526 | def test_no_provider_type(self): | ||
455 | 527 | # A ValueError is raised if the provider type is not included in the | ||
456 | 528 | # environment info. | ||
457 | 529 | contents = yaml.safe_dump({'environments': {'aws': {}}}) | ||
458 | 530 | env_file = self.make_env_file(contents) | ||
459 | 531 | expected = 'aws provider type not found in {}'.format(env_file) | ||
460 | 532 | with self.assert_value_error(expected): | ||
461 | 533 | utils.parse_env_file(env_file, 'aws') | ||
462 | 534 | |||
463 | 535 | def test_no_admin_secret(self): | ||
464 | 536 | # A ValueError is raised if the admin secret is not included in the | ||
465 | 537 | # environment info. | ||
466 | 538 | contents = yaml.safe_dump({'environments': {'aws': {'type': 'ec2'}}}) | ||
467 | 539 | env_file = self.make_env_file(contents) | ||
468 | 540 | expected = 'aws admin secret not found in {}'.format(env_file) | ||
469 | 541 | with self.assert_value_error(expected): | ||
470 | 542 | utils.parse_env_file(env_file, 'aws') | ||
471 | 543 | |||
472 | 544 | def test_success(self): | ||
473 | 545 | # The environment provider type and admin secret are returned. | ||
474 | 546 | env_file = self.make_env_file() | ||
475 | 547 | env_type, admin_secret = utils.parse_env_file(env_file, 'aws') | ||
476 | 548 | self.assertEqual('ec2', env_type) | ||
477 | 549 | self.assertEqual('Secret!', admin_secret) | ||
478 | 550 | |||
479 | 551 | |||
480 | 552 | class TestParseStatusOutput(helpers.ValueErrorTestsMixin, unittest.TestCase): | 441 | class TestParseStatusOutput(helpers.ValueErrorTestsMixin, unittest.TestCase): |
481 | 553 | 442 | ||
482 | 554 | def test_invalid_yaml(self): | 443 | def test_invalid_yaml(self): |
483 | 555 | 444 | ||
484 | === modified file 'quickstart/utils.py' | |||
485 | --- quickstart/utils.py 2013-11-25 18:07:47 +0000 | |||
486 | +++ quickstart/utils.py 2013-12-04 13:19:54 +0000 | |||
487 | @@ -21,22 +21,15 @@ | |||
488 | 21 | import httplib | 21 | import httplib |
489 | 22 | import json | 22 | import json |
490 | 23 | import logging | 23 | import logging |
491 | 24 | import os | ||
492 | 25 | import pipes | 24 | import pipes |
493 | 26 | import re | ||
494 | 27 | import socket | 25 | import socket |
495 | 28 | import subprocess | 26 | import subprocess |
496 | 29 | import urllib2 | 27 | import urllib2 |
497 | 30 | 28 | ||
498 | 31 | import yaml | 29 | import yaml |
499 | 32 | 30 | ||
507 | 33 | from quickstart import ( | 31 | from quickstart import settings |
508 | 34 | charms, | 32 | from quickstart.models import charms |
502 | 35 | settings | ||
503 | 36 | ) | ||
504 | 37 | |||
505 | 38 | # Compile the regular expression used to parse the "juju switch" output. | ||
506 | 39 | _juju_switch_expression = re.compile(r'Current environment: "([\w-]+)"\n') | ||
509 | 40 | 33 | ||
510 | 41 | 34 | ||
511 | 42 | def add_apt_repository(repository): | 35 | def add_apt_repository(repository): |
512 | @@ -134,35 +127,6 @@ | |||
513 | 134 | return charm_url | 127 | return charm_url |
514 | 135 | 128 | ||
515 | 136 | 129 | ||
516 | 137 | def get_default_env_name(): | ||
517 | 138 | """Return the current Juju environment name. | ||
518 | 139 | |||
519 | 140 | The environment name can be set either by | ||
520 | 141 | - setting the JUJU_ENV environment variable; | ||
521 | 142 | - using "juju switch my-env-name"; | ||
522 | 143 | - setting the default environment in the environments.yaml file. | ||
523 | 144 | The former overrides the latter. | ||
524 | 145 | |||
525 | 146 | Return None if a default environment is not found. | ||
526 | 147 | """ | ||
527 | 148 | env_name = os.getenv('JUJU_ENV', '').strip() | ||
528 | 149 | if env_name: | ||
529 | 150 | return env_name | ||
530 | 151 | # XXX 2013-10-16 frankban bug=1193244: | ||
531 | 152 | # Support the new behavior of juju-switch, currently under development: | ||
532 | 153 | # the command will just output the environment name, or exit with an | ||
533 | 154 | # error if no default environment is configured. | ||
534 | 155 | # The "juju switch" command parses ~/.juju/current-environment file. If the | ||
535 | 156 | # environment name is not found there, then it tries to retrieve the name | ||
536 | 157 | # from the "default" section of the ~/.juju/environments.yaml file. | ||
537 | 158 | retcode, output, _ = call('juju', 'switch') | ||
538 | 159 | if retcode: | ||
539 | 160 | return None | ||
540 | 161 | match = _juju_switch_expression.match(output) | ||
541 | 162 | if match is not None: | ||
542 | 163 | return match.groups()[0] | ||
543 | 164 | |||
544 | 165 | |||
545 | 166 | def get_service_info(status, service_name): | 130 | def get_service_info(status, service_name): |
546 | 167 | """Retrieve information on the given service and on its first alive unit. | 131 | """Retrieve information on the given service and on its first alive unit. |
547 | 168 | 132 | ||
548 | @@ -258,53 +222,6 @@ | |||
549 | 258 | return bundle_name, bundle_services | 222 | return bundle_name, bundle_services |
550 | 259 | 223 | ||
551 | 260 | 224 | ||
552 | 261 | def parse_env_file(env_file, env_name): | ||
553 | 262 | """Parse the provided Juju environments.yaml file. | ||
554 | 263 | |||
555 | 264 | Return a tuple containing the provider type and the admin secret associated | ||
556 | 265 | with the given environment name. | ||
557 | 266 | |||
558 | 267 | Raise a ValueError if: | ||
559 | 268 | - the environment file is not found; | ||
560 | 269 | - the environment file contents are not parsable by YAML; | ||
561 | 270 | - the YAML contents are not properly structured; | ||
562 | 271 | - the environment named envname is not found; | ||
563 | 272 | - the environment does not include the "type" field; | ||
564 | 273 | - the environment does not include the "admin_secret" field. | ||
565 | 274 | """ | ||
566 | 275 | # Load the Juju environments file. | ||
567 | 276 | try: | ||
568 | 277 | environments_file = open(env_file) | ||
569 | 278 | except IOError as err: | ||
570 | 279 | msg = 'unable to open environments file: {}'.format(err) | ||
571 | 280 | raise ValueError(msg) | ||
572 | 281 | # Parse the Juju environments file. | ||
573 | 282 | try: | ||
574 | 283 | environments = yaml.safe_load(environments_file) | ||
575 | 284 | except Exception as err: | ||
576 | 285 | msg = 'unable to parse environments file {}: {}'.format(env_file, err) | ||
577 | 286 | raise ValueError(msg) | ||
578 | 287 | # Retrieve the information about the current environment. | ||
579 | 288 | try: | ||
580 | 289 | environment = environments.get('environments', {}).get(env_name) | ||
581 | 290 | except AttributeError as err: | ||
582 | 291 | msg = 'invalid YAML contents in {}: {}'.format(env_file, environments) | ||
583 | 292 | raise ValueError(msg) | ||
584 | 293 | if environment is None: | ||
585 | 294 | msg = 'environment {} not found in {}'.format(env_name, env_file) | ||
586 | 295 | raise ValueError(msg) | ||
587 | 296 | # Retrieve the provider type and the admin secret. | ||
588 | 297 | env_type = environment.get('type') | ||
589 | 298 | if env_type is None: | ||
590 | 299 | msg = '{} provider type not found in {}'.format(env_name, env_file) | ||
591 | 300 | raise ValueError(msg) | ||
592 | 301 | admin_secret = environment.get('admin-secret') | ||
593 | 302 | if admin_secret is None: | ||
594 | 303 | msg = '{} admin secret not found in {}'.format(env_name, env_file) | ||
595 | 304 | raise ValueError(msg) | ||
596 | 305 | return env_type, admin_secret | ||
597 | 306 | |||
598 | 307 | |||
599 | 308 | def parse_status_output(output): | 225 | def parse_status_output(output): |
600 | 309 | """Parse the output of juju status. | 226 | """Parse the output of juju status. |
601 | 310 | 227 |
Reviewers: mp+197709_ code.launchpad. net,
Message:
Please take a look.
Description:
Envs management: code reorganization.
This branch includes code reorg as
a preparation for the upcoming envs
management work.
It's just about mechanically moving code environment in
to the new models package, except for a
minor fix to test_init_
test_manage.py.
https:/ /code.launchpad .net/~frankban/ juju-quickstart /env-management -1/+merge/ 197709
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/37150043/
Affected files (+269, -202 lines): manage. py models/ __init_ _.py models/ charms. py models/ envs.py tests/models/ __init_ _.py tests/models/ test_charms. py tests/models/ test_envs. py tests/test_ manage. py tests/test_ utils.py
A [revision details]
M quickstart/
A quickstart/
M quickstart/
A quickstart/
A quickstart/
M quickstart/
A quickstart/
M quickstart/
M quickstart/
M quickstart/utils.py