Merge lp:~frankban/juju-quickstart/apt-update-fixes into lp:juju-quickstart
- apt-update-fixes
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 124 |
Proposed branch: | lp:~frankban/juju-quickstart/apt-update-fixes |
Merge into: | lp:juju-quickstart |
Diff against target: |
363 lines (+102/-23) 6 files modified
quickstart/models/bundles.py (+9/-6) quickstart/platform_support.py (+6/-2) quickstart/tests/models/test_bundles.py (+48/-6) quickstart/tests/test_app.py (+26/-4) quickstart/tests/test_utils.py (+12/-5) quickstart/utils.py (+1/-0) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/apt-update-fixes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+252430@code.launchpad.net |
Commit message
Description of the change
Always retrieve bundle from new cs endpoint.
This is a follow up from previous branch,
and also includes the remaining bits before
2.0.1 release.
Even if a legacy bundle is specified, and even
if the legacy bundle data is retrieved for name
validation, always use the new bundle.yaml
charm store endpoint when returning the bundle
contents.
This branch also include a drive by fix to always
run `apt-get update` before installing packages,
even in the case --distro-only is enabled.
Tests: `make check`.
QA:
install bundles with quickstart:
`devenv/
Try the following bundles:
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
Those instead should return errors:
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
Thank you!
Francesco Banconi (frankban) wrote : | # |
Brad Crittenden (bac) wrote : | # |
Code LGTM. No QA yet.
Richard Harding (rharding) wrote : | # |
LGTM no qa atm
Richard Harding (rharding) wrote : | # |
On 2015/03/10 13:55:46, rharding wrote:
> LGTM no qa atm
QA OK thanks!
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Always retrieve bundle from new cs endpoint.
This is a follow up from previous branch,
and also includes the remaining bits before
2.0.1 release.
Even if a legacy bundle is specified, and even
if the legacy bundle data is retrieved for name
validation, always use the new bundle.yaml
charm store endpoint when returning the bundle
contents.
This branch also include a drive by fix to always
run `apt-get update` before installing packages,
even in the case --distro-only is enabled.
Tests: `make check`.
QA:
install bundles with quickstart:
`devenv/
Try the following bundles:
- devenv/
- devenv/
- devenv/
- devenv/
bundle:
- devenv/
Those instead should return errors:
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
bundle:
Thank you!
R=bac, rharding
CC=
https:/
Francesco Banconi (frankban) wrote : | # |
Thanks for the quick reviews and QA Brad and Rick!
Preview Diff
1 | === modified file 'quickstart/models/bundles.py' |
2 | --- quickstart/models/bundles.py 2015-03-10 10:18:58 +0000 |
3 | +++ quickstart/models/bundles.py 2015-03-10 13:22:58 +0000 |
4 | @@ -142,7 +142,10 @@ |
5 | """ |
6 | if source.startswith('bundle:'): |
7 | # The source refers to an old style bundle URL. |
8 | - return _bundle_from_charmworld_url(source) |
9 | + try: |
10 | + return _bundle_from_charmworld_url(source) |
11 | + except charmstore.NotFoundError as err: |
12 | + raise IOError(bytes(err)) |
13 | |
14 | has_extension = source.endswith('.yaml') or source.endswith('.json') |
15 | is_remote = source.startswith('http://') or source.startswith('https://') |
16 | @@ -200,6 +203,7 @@ |
17 | content is not valid. |
18 | Raise a IOError if a problem is encountered while fetching the YAML |
19 | content from the charm store. |
20 | + Raise a charmstore.NotFoundError if the bundle is not found. |
21 | """ |
22 | match = _charmworld_url_expression.match(url) |
23 | if match is None: |
24 | @@ -224,11 +228,10 @@ |
25 | # in the original YAML is also required. |
26 | reference = references.Reference( |
27 | 'cs', user, 'bundle', basket, revision) |
28 | - try: |
29 | - data = charmstore.get_legacy_bundle_data(reference) |
30 | - except charmstore.NotFoundError as err: |
31 | - raise IOError(bytes(err)) |
32 | - data = _flatten_data(data, name) |
33 | + # Validate the bundle name is included in the legacy data. |
34 | + _flatten_data(charmstore.get_legacy_bundle_data(reference), name) |
35 | + # Retrieve the new bundle data corresponding to the shorter reference. |
36 | + data = charmstore.get_bundle_data(reference) |
37 | |
38 | validate(data) |
39 | # XXX frankban 2015-02-26: remove this when switching to the new bundle |
40 | |
41 | === modified file 'quickstart/platform_support.py' |
42 | --- quickstart/platform_support.py 2015-01-30 15:27:07 +0000 |
43 | +++ quickstart/platform_support.py 2015-03-10 13:22:58 +0000 |
44 | @@ -83,10 +83,14 @@ |
45 | print('sudo privileges will be used for the installation of \n' |
46 | 'the following packages: {}\n' |
47 | 'this can take a while...'.format(', '.join(required_packages))) |
48 | + if distro_only: |
49 | + retcode, _, error = utils.call('sudo', '/usr/bin/apt-get', 'update') |
50 | + if retcode: |
51 | + raise OSError(error.encode('utf-8')) |
52 | retcode, _, error = utils.call( |
53 | 'sudo', '/usr/bin/apt-get', 'install', '-y', *required_packages) |
54 | if retcode: |
55 | - raise OSError(bytes(error)) |
56 | + raise OSError(error.encode('utf-8')) |
57 | |
58 | |
59 | def _installer_osx(distro_only, required_packages): |
60 | @@ -102,7 +106,7 @@ |
61 | retcode, _, error = utils.call( |
62 | '/usr/local/bin/brew', 'install', *required_packages) |
63 | if retcode: |
64 | - raise OSError(bytes(error)) |
65 | + raise OSError(error.encode('utf-8')) |
66 | |
67 | |
68 | INSTALLERS = { |
69 | |
70 | === modified file 'quickstart/tests/models/test_bundles.py' |
71 | --- quickstart/tests/models/test_bundles.py 2015-03-09 17:50:28 +0000 |
72 | +++ quickstart/tests/models/test_bundles.py 2015-03-10 13:22:58 +0000 |
73 | @@ -121,8 +121,10 @@ |
74 | side_effect = [ |
75 | # A first call urlread returns a not found error. |
76 | netutils.NotFoundError('boo!'), |
77 | - # A second call succeeds. |
78 | + # A second call to retrieve the legacy data succeeds. |
79 | self.legacy_bundle_content, |
80 | + # The third call to retrieve the new data succeeds. |
81 | + self.bundle_content, |
82 | ] |
83 | with self.patch_urlread(error=side_effect) as mock_urlread: |
84 | bundle = bundles.from_source('bundle:mediawiki/bundle1') |
85 | @@ -132,10 +134,11 @@ |
86 | # XXX frankban 2015-03-09: remove this check once we get rid of the |
87 | # charmworld id concept. |
88 | self.assertEqual('mediawiki/bundle1', bundle.reference.charmworld_id) |
89 | - # The urlread function has been called two times: the first time |
90 | + # The urlread function has been called three times: the first time |
91 | # including both the basket and the bundle name, the second time |
92 | - # to retrieve the legacy bundle yaml, only including the basket. |
93 | - self.assertEqual(mock_urlread.call_count, 2) |
94 | + # to retrieve the legacy bundle YAML, the third time to retrieve the |
95 | + # new bundle YAML (this time without including the basket name). |
96 | + self.assertEqual(len(side_effect), mock_urlread.call_count) |
97 | mock_urlread.assert_has_calls([ |
98 | mock.call( |
99 | settings.CHARMSTORE_API + |
100 | @@ -143,6 +146,9 @@ |
101 | mock.call( |
102 | settings.CHARMSTORE_API + |
103 | 'bundle/mediawiki/archive/bundles.yaml.orig'), |
104 | + mock.call( |
105 | + settings.CHARMSTORE_API + |
106 | + 'bundle/mediawiki/archive/bundle.yaml'), |
107 | ]) |
108 | |
109 | def test_charmworld_bundle_deprecation_warning(self): |
110 | @@ -193,7 +199,7 @@ |
111 | # The urlread function has been called two times: the first time |
112 | # including both the basket and the bundle name, the second time |
113 | # to retrieve the legacy bundle yaml, only including the basket. |
114 | - self.assertEqual(mock_urlread.call_count, 2) |
115 | + self.assertEqual(2, mock_urlread.call_count) |
116 | mock_urlread.assert_has_calls([ |
117 | mock.call( |
118 | settings.CHARMSTORE_API + |
119 | @@ -220,7 +226,7 @@ |
120 | # The urlread function has been called two times: the first time |
121 | # including both the basket and the bundle name, the second time |
122 | # to retrieve the legacy bundle yaml, only including the basket. |
123 | - self.assertEqual(mock_urlread.call_count, 2) |
124 | + self.assertEqual(len(side_effect), mock_urlread.call_count) |
125 | mock_urlread.assert_has_calls([ |
126 | mock.call( |
127 | settings.CHARMSTORE_API + |
128 | @@ -230,6 +236,42 @@ |
129 | 'bundle/django/archive/bundles.yaml.orig'), |
130 | ]) |
131 | |
132 | + def test_charmworld_bundle_from_legacy_not_found_error(self): |
133 | + # An IOError is raised if the legacy bundle cannot be found. |
134 | + side_effect = [ |
135 | + # A first call urlread returns a not found error. |
136 | + netutils.NotFoundError('boo!'), |
137 | + # A second call to retrieve the legacy bundle data succeeds. |
138 | + self.legacy_bundle_content, |
139 | + # The third call to get the bundle from the new API endpoint fails. |
140 | + # Note that this is unlikely to happen. |
141 | + netutils.NotFoundError('what!'), |
142 | + ] |
143 | + expected_error = ( |
144 | + 'charm store resource not found at ' |
145 | + '{}bundle/django/archive/bundle.yaml: ' |
146 | + 'what!'.format(settings.CHARMSTORE_API)) |
147 | + with self.patch_urlread(error=side_effect) as mock_urlread: |
148 | + with self.assertRaises(IOError) as ctx: |
149 | + bundles.from_source('bundle:django/bundle1') |
150 | + self.assertEqual(expected_error, bytes(ctx.exception)) |
151 | + # The urlread function has been called three times: the first time |
152 | + # including both the basket and the bundle name, the second time |
153 | + # to retrieve the legacy bundle YAML, the third time to retrieve the |
154 | + # new bundle YAML (this time without including the basket name). |
155 | + self.assertEqual(len(side_effect), mock_urlread.call_count) |
156 | + mock_urlread.assert_has_calls([ |
157 | + mock.call( |
158 | + settings.CHARMSTORE_API + |
159 | + 'bundle/django-bundle1/archive/bundle.yaml'), |
160 | + mock.call( |
161 | + settings.CHARMSTORE_API + |
162 | + 'bundle/django/archive/bundles.yaml.orig'), |
163 | + mock.call( |
164 | + settings.CHARMSTORE_API + |
165 | + 'bundle/django/archive/bundle.yaml'), |
166 | + ]) |
167 | + |
168 | def test_jujucharms_bundle(self): |
169 | # A bundle instance is properly returned from a jujucharms.com id. |
170 | with self.patch_urlread(contents=self.bundle_content) as mock_urlread: |
171 | |
172 | === modified file 'quickstart/tests/test_app.py' |
173 | --- quickstart/tests/test_app.py 2015-03-09 17:50:28 +0000 |
174 | +++ quickstart/tests/test_app.py 2015-03-10 13:22:58 +0000 |
175 | @@ -98,9 +98,10 @@ |
176 | side_effects = ( |
177 | (127, '', 'no juju'), # Retrieve the Juju version. |
178 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
179 | + (0, 'update 1', ''), # Update the repository with new sources. |
180 | (0, 'install add repo', ''), # Install add-apt-repository. |
181 | (0, 'add repo', ''), # Add the juju stable repository. |
182 | - (0, 'update', ''), # Update the repository with new sources. |
183 | + (0, 'update 2', ''), # Update the repository with new sources. |
184 | (0, 'install', ''), # Install missing packages. |
185 | (0, '1.18.0', ''), # Retrieve the version again. |
186 | ) |
187 | @@ -109,6 +110,7 @@ |
188 | mock_call.assert_has_calls([ |
189 | mock.call(self.juju_command, 'version'), |
190 | mock.call('lsb_release', '-cs'), |
191 | + mock.call('sudo', self.apt_get, 'update'), |
192 | mock.call('sudo', self.apt_get, 'install', '-y', |
193 | 'software-properties-common'), |
194 | mock.call('sudo', self.add_repository, '-y', 'ppa:juju/stable'), |
195 | @@ -161,6 +163,7 @@ |
196 | # All the missing packages are installed from the distro repository. |
197 | side_effects = ( |
198 | (127, '', 'no juju'), # Retrieve the Juju version. |
199 | + (0, 'update', ''), # Update the repository with new sources. |
200 | (0, 'install', ''), # Install missing packages. |
201 | (0, '1.17.42', ''), # Retrieve the version again. |
202 | ) |
203 | @@ -169,6 +172,7 @@ |
204 | self.assertEqual(len(side_effects), mock_call.call_count) |
205 | mock_call.assert_has_calls([ |
206 | mock.call(self.juju_command, 'version'), |
207 | + mock.call('sudo', self.apt_get, 'update'), |
208 | mock.call('sudo', self.apt_get, 'install', '-y', |
209 | 'juju-core', 'juju-local'), |
210 | mock.call(self.juju_command, 'version'), |
211 | @@ -199,9 +203,10 @@ |
212 | (0, '1.16.42', ''), # Check the juju command. |
213 | (127, '', 'no lxc'), # Check the lxc-ls command. |
214 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
215 | + (0, 'update 1', ''), # Update the repository with new sources. |
216 | (0, 'install add repo', ''), # Install add-apt-repository. |
217 | (0, 'add repo', ''), # Add the juju stable repository. |
218 | - (0, 'update', ''), # Update the repository with new sources. |
219 | + (0, 'update 2', ''), # Update the repository with new sources. |
220 | (0, 'install', ''), # Install missing packages. |
221 | ) |
222 | mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
223 | @@ -210,6 +215,7 @@ |
224 | mock.call(self.juju_command, 'version'), |
225 | mock.call('/usr/bin/lxc-ls'), |
226 | mock.call('lsb_release', '-cs'), |
227 | + mock.call('sudo', self.apt_get, 'update'), |
228 | mock.call('sudo', self.apt_get, 'install', '-y', |
229 | 'software-properties-common'), |
230 | mock.call('sudo', self.add_repository, '-y', 'ppa:juju/stable'), |
231 | @@ -231,6 +237,7 @@ |
232 | side_effects = ( |
233 | (0, '1.16.42', ''), # Check the juju command. |
234 | (127, '', 'no lxc'), # Check the lxc-ls command. |
235 | + (0, 'update', ''), # Update the repository with new sources. |
236 | (0, 'install', ''), # Install missing packages. |
237 | ) |
238 | mock_call, juju_version = self.call_ensure_dependencies( |
239 | @@ -239,6 +246,7 @@ |
240 | mock_call.assert_has_calls([ |
241 | mock.call(self.juju_command, 'version'), |
242 | mock.call('/usr/bin/lxc-ls'), |
243 | + mock.call('sudo', self.apt_get, 'update'), |
244 | mock.call('sudo', self.apt_get, 'install', '-y', 'juju-local'), |
245 | ]) |
246 | mock_print.assert_called_once_with( |
247 | @@ -252,6 +260,7 @@ |
248 | side_effects = ( |
249 | (127, '', 'no juju'), # Check the juju command. |
250 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
251 | + (0, 'update', ''), # Update the repository with new sources. |
252 | (0, 'install add repo', ''), # Install add-apt-repository. |
253 | (1, '', 'add repo error'), # Add the juju stable repository. |
254 | ) |
255 | @@ -264,15 +273,27 @@ |
256 | side_effects = ( |
257 | (127, '', 'no juju'), # Check the juju command. |
258 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
259 | + (0, 'update 1', ''), # Update the repository with new sources. |
260 | (0, 'install add repo', ''), # Install add-apt-repository. |
261 | (0, 'add repo', ''), # Add the juju stable repository. |
262 | - (0, 'update', ''), # Update the repository with new sources. |
263 | + (0, 'update 2', ''), # Update the repository with new sources. |
264 | (1, '', 'install error'), # Install missing packages. |
265 | ) |
266 | with self.assert_program_exit('install error'): |
267 | mock_call = self.call_ensure_dependencies(side_effects)[0] |
268 | self.assertEqual(3, mock_call.call_count) |
269 | |
270 | + def test_distro_only_update_failure_apt(self, mock_print): |
271 | + # A ProgramExit is raised if updating APT sources fails. |
272 | + side_effects = ( |
273 | + (127, '', 'no juju'), # Retrieve the Juju version. |
274 | + (1, '', 'update error'), # Update the repository with new sources. |
275 | + ) |
276 | + with self.assert_program_exit('update error'): |
277 | + mock_call = self.call_ensure_dependencies( |
278 | + side_effects, distro_only=True)[0] |
279 | + self.assertEqual(2, mock_call.call_count) |
280 | + |
281 | def test_failed_install_osx(self, mock_print): |
282 | # A ProgramExit is raised if the packages installation fails. |
283 | side_effects = ( |
284 | @@ -292,9 +313,10 @@ |
285 | side_effects = ( |
286 | (127, '', 'no juju'), # Check the juju command. |
287 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
288 | + (0, 'update 1', ''), # Update the repository with new sources. |
289 | (0, 'install add repo', ''), # Install add-apt-repository. |
290 | (0, 'add repo', ''), # Add the juju stable repository. |
291 | - (0, 'update', ''), # Update the repository with new sources. |
292 | + (0, 'update 2', ''), # Update the repository with new sources. |
293 | (0, 'install', ''), # Install missing packages. |
294 | (127, '', 'no juju (again)'), # Retrieve the Juju version. |
295 | ) |
296 | |
297 | === modified file 'quickstart/tests/test_utils.py' |
298 | --- quickstart/tests/test_utils.py 2015-02-09 12:34:33 +0000 |
299 | +++ quickstart/tests/test_utils.py 2015-03-10 13:22:58 +0000 |
300 | @@ -40,9 +40,10 @@ |
301 | apt_get = '/usr/bin/apt-get' |
302 | repository = 'ppa:good/stuff' |
303 | side_effects = ( |
304 | + (0, 'first update', ''), # Update the global repository. |
305 | (0, 'apt-get install', ''), # Install add-apt-repository. |
306 | (0, 'add-apt-repository', ''), # Add the repository. |
307 | - (0, 'update', ''), # Update the global repository |
308 | + (0, 'second update', ''), # Update the global repository. |
309 | ) |
310 | |
311 | def patch_codename(self, codename): |
312 | @@ -59,6 +60,7 @@ |
313 | mock_get_ubuntu_codename.assert_called_once_with() |
314 | self.assertEqual(len(self.side_effects), mock_call.call_count) |
315 | mock_call.assert_has_calls([ |
316 | + mock.call('sudo', self.apt_get, 'update'), |
317 | mock.call('sudo', self.apt_get, 'install', '-y', |
318 | 'python-software-properties'), |
319 | mock.call('sudo', self.apt_add_repository, '-y', self.repository), |
320 | @@ -73,6 +75,7 @@ |
321 | mock_get_ubuntu_codename.assert_called_once_with() |
322 | self.assertEqual(len(self.side_effects), mock_call.call_count) |
323 | mock_call.assert_has_calls([ |
324 | + mock.call('sudo', self.apt_get, 'update'), |
325 | mock.call('sudo', self.apt_get, 'install', '-y', |
326 | 'software-properties-common'), |
327 | mock.call('sudo', self.apt_add_repository, '-y', self.repository), |
328 | @@ -92,15 +95,19 @@ |
329 | |
330 | def test_command_error(self, mock_print): |
331 | # An OSError is raised if a command error occurs. |
332 | - side_effects = [(1, '', 'apt-get install error')] |
333 | + side_effects = [(0, 'update', ''), (1, '', 'apt-get install error')] |
334 | with self.patch_codename('quantal') as mock_get_ubuntu_codename: |
335 | with self.patch_multiple_calls(side_effects) as mock_call: |
336 | with self.assertRaises(OSError) as context_manager: |
337 | utils.add_apt_repository(self.repository) |
338 | mock_get_ubuntu_codename.assert_called_once_with() |
339 | - mock_call.assert_called_once_with( |
340 | - 'sudo', self.apt_get, 'install', '-y', |
341 | - 'software-properties-common') |
342 | + self.assertEqual(len(side_effects), mock_call.call_count) |
343 | + mock_call.assert_has_calls([ |
344 | + mock.call('sudo', self.apt_get, 'update'), |
345 | + mock.call( |
346 | + 'sudo', self.apt_get, 'install', '-y', |
347 | + 'software-properties-common'), |
348 | + ]) |
349 | self.assertEqual( |
350 | 'apt-get install error', bytes(context_manager.exception)) |
351 | |
352 | |
353 | === modified file 'quickstart/utils.py' |
354 | --- quickstart/utils.py 2015-03-10 10:18:58 +0000 |
355 | +++ quickstart/utils.py 2015-03-10 13:22:58 +0000 |
356 | @@ -48,6 +48,7 @@ |
357 | if get_ubuntu_codename() == 'precise': |
358 | add_repository_package = 'python-software-properties' |
359 | commands = ( |
360 | + ('/usr/bin/apt-get', 'update'), |
361 | ('/usr/bin/apt-get', 'install', '-y', add_repository_package), |
362 | ('/usr/bin/add-apt-repository', '-y', repository), |
363 | ('/usr/bin/apt-get', 'update'), |
Reviewers: mp+252430_ code.launchpad. net,
Message:
Please take a look.
Description:
Always retrieve bundle from new cs endpoint.
This is a follow up from previous branch,
and also includes the remaining bits before
2.0.1 release.
Even if a legacy bundle is specified, and even
if the legacy bundle data is retrieved for name
validation, always use the new bundle.yaml
charm store endpoint when returning the bundle
contents.
This branch also include a drive by fix to always
run `apt-get update` before installing packages,
even in the case --distro-only is enabled.
Tests: `make check`.
QA: bin/juju- quickstart {bundle}` bin/juju- quickstart mediawiki-single bin/juju- quickstart u/bigdata- dev/apache- analytics- sql bin/juju- quickstart bundle: mediawiki/ scalable bin/juju- quickstart ~landscape/ landscape- dense-maas/ landscape- dense-maas bin/juju- quickstart bundle: django/ example- single
install bundles with quickstart:
`devenv/
Try the following bundles:
- devenv/
- devenv/
- devenv/
- devenv/
bundle:
- devenv/
Those instead should return errors: bin/juju- quickstart mediawiki/trusty bin/juju- quickstart mediawiki-nosuch bin/juju- quickstart no-such bin/juju- quickstart bundle:no/such bin/juju- quickstart bundle:invalid bin/juju- quickstart ~landscape/ landscape- dense-maas/ landscape
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
- devenv/
bundle:
Thank you!
https:/ /code.launchpad .net/~frankban/ juju-quickstart /apt-update- fixes/+ merge/252430
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/215750043/
Affected files (+104, -23 lines): models/ bundles. py platform_ support. py tests/models/ test_bundles. py tests/test_ app.py tests/test_ utils.py
A [revision details]
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/utils.py