Merge lp:~smoser/software-properties/shortcut-refactor into lp:software-properties
- shortcut-refactor
- Merge into main
Status: | Merged |
---|---|
Merged at revision: | 877 |
Proposed branch: | lp:~smoser/software-properties/shortcut-refactor |
Merge into: | lp:software-properties |
Diff against target: |
687 lines (+399/-109) 6 files modified
add-apt-repository (+42/-82) debian/changelog (+7/-0) softwareproperties/SoftwareProperties.py (+48/-20) softwareproperties/cloudarchive.py (+123/-0) softwareproperties/ppa.py (+122/-7) softwareproperties/shortcuts.py (+57/-0) |
To merge this branch: | bzr merge lp:~smoser/software-properties/shortcut-refactor |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Pending | ||
Ubuntu Core Development Team | Pending | ||
Review via email: mp+188500@code.launchpad.net |
Commit message
Refactor ppa handling into generic 'shortcuts', add handler for cloud-archive
This re-factors the 'ppa:' handling into something more generic.
Then, we add a handler for 'cloud-
fixes bug 1233486
Description of the change
We're wanting to get this into saucy, even though we only expect for it to be used on precise. We'd then backport the code and SRU it to 12.04 to make users of the cloud archive able to use it.
Please don't let that affect your review. I think it should stand on its own as a rework of the 'ppa:' code.
Michael Vogt (mvo) wrote : | # |
Scott Moser (smoser) wrote : | # |
I'll fix. thanks.
Scott Moser (smoser) wrote : | # |
I've fixed the test case.
- 881. By Scott Moser
-
fix test case, make AddPPASigningKe
yThread more correct.
Scott Moser (smoser) wrote : | # |
Michael,
the test case is fixed now, please do review. and accept or merge if you see fit.
Thanks.
- 882. By Scott Moser
-
support adding cloud-archive repositories using syntax like
cloud-archive:havana (LP: #1233486)
Preview Diff
1 | === modified file 'add-apt-repository' |
2 | --- add-apt-repository 2013-05-28 19:52:17 +0000 |
3 | +++ add-apt-repository 2013-10-05 13:08:15 +0000 |
4 | @@ -8,50 +8,14 @@ |
5 | import gettext |
6 | import locale |
7 | |
8 | -from softwareproperties.SoftwareProperties import SoftwareProperties |
9 | -from softwareproperties.ppa import DEFAULT_KEYSERVER, expand_ppa_line |
10 | -from softwareproperties import lp_application_name |
11 | +from softwareproperties.SoftwareProperties import SoftwareProperties, shortcut_handler |
12 | +from softwareproperties.shortcuts import ShortcutException |
13 | +from softwareproperties.ppa import DEFAULT_KEYSERVER |
14 | import aptsources |
15 | from aptsources.sourceslist import SourceEntry |
16 | -from aptsources.distro import * |
17 | from optparse import OptionParser |
18 | from gettext import gettext as _ |
19 | |
20 | -try: |
21 | - from urllib.error import HTTPError, URLError |
22 | -except ImportError: |
23 | - import pycurl |
24 | - HTTPError = pycurl.error |
25 | - |
26 | -def _maybe_suggest_ppa_name_based_on_user(user): |
27 | - try: |
28 | - from launchpadlib.launchpad import Launchpad |
29 | - lp = Launchpad.login_anonymously(lp_application_name, "production") |
30 | - try: |
31 | - user_inst = lp.people[user] |
32 | - entity_name = _("team") if user_inst.is_team else _("user") |
33 | - if len(user_inst.ppas) > 0: |
34 | - # Translators: %(entity)s is either "team" or "user" |
35 | - print(_("The %(entity)s named '%(user)s' has no PPA named '%(ppa)s'") % { |
36 | - 'entity' : entity_name, |
37 | - 'user' : user, |
38 | - 'ppa' : ppa_name}) |
39 | - print(_("Please choose from the following available PPAs:")) |
40 | - for ppa in user_inst.ppas: |
41 | - print(_(" * '%(name)s': %(displayname)s") % { |
42 | - 'name' : ppa.name, |
43 | - 'displayname' : ppa.displayname}) |
44 | - else: |
45 | - # Translators: %(entity)s is either "team" or "user" |
46 | - print(_("The %(entity)s named '%(user)s' does not have any PPA") % { |
47 | - 'entity' : entity_name, |
48 | - 'user' : user}) |
49 | - except KeyError: |
50 | - pass |
51 | - except ImportError: |
52 | - print(_("Please check that the PPA name or format is correct.")) |
53 | - |
54 | - |
55 | if __name__ == "__main__": |
56 | # Force encoding to UTF-8 even in non-UTF-8 locales. |
57 | sys.stdout = io.TextIOWrapper( |
58 | @@ -123,46 +87,6 @@ |
59 | # get the line |
60 | line = args[0] |
61 | |
62 | - # display PPA info (if needed) |
63 | - if line.startswith("ppa:") and not options.assume_yes: |
64 | - from softwareproperties.ppa import PPAException, get_ppa_info_from_lp, LAUNCHPAD_PPA_API |
65 | - user, sep, ppa_name = line.split(":")[1].partition("/") |
66 | - ppa_name = ppa_name or "ppa" |
67 | - try: |
68 | - ppa_info = get_ppa_info_from_lp(user, ppa_name) |
69 | - except HTTPError: |
70 | - print(_("Cannot add PPA: '%s'.") % line) |
71 | - if user.startswith("~"): |
72 | - print(_("Did you mean 'ppa:%s/%s' ?") %(user[1:], ppa_name)) |
73 | - sys.exit(1) # Exit because the user cannot be correct |
74 | - # If the PPA does not exist, then try to find if the user/team |
75 | - # exists. If it exists, list down the PPAs |
76 | - _maybe_suggest_ppa_name_based_on_user(user) |
77 | - sys.exit(1) |
78 | - except (ValueError, PPAException): |
79 | - print(_("Cannot access PPA (%s) to get PPA information, " |
80 | - "please check your internet connection.") % \ |
81 | - (LAUNCHPAD_PPA_API % (user, ppa_name))) |
82 | - sys.exit(1) |
83 | - # private PPAs are not supported |
84 | - if "private" in ppa_info and ppa_info["private"]: |
85 | - print(_("Adding private PPAs is not supported currently")) |
86 | - sys.exit(1) |
87 | - |
88 | - if options.remove: |
89 | - print(_("You are about to remove the following PPA from your system:")) |
90 | - else: |
91 | - print(_("You are about to add the following PPA to your system:")) |
92 | - print(" %s" % (ppa_info["description"] or "")) |
93 | - print(_(" More info: %s") % str(ppa_info["web_link"])) |
94 | - if (sys.stdin.isatty() and |
95 | - not "FORCE_ADD_APT_REPOSITORY" in os.environ): |
96 | - if options.remove: |
97 | - print(_("Press [ENTER] to continue or ctrl-c to cancel removing it")) |
98 | - else: |
99 | - print(_("Press [ENTER] to continue or ctrl-c to cancel adding it")) |
100 | - sys.stdin.readline() |
101 | - |
102 | # add it |
103 | sp = SoftwareProperties(options=options) |
104 | distro = aptsources.distro.get_distro() |
105 | @@ -188,8 +112,39 @@ |
106 | sp.sourceslist.save() |
107 | sys.exit(0) |
108 | |
109 | + # this wasn't a component name ('multiverse', 'backports'), so its either |
110 | + # a actual line to be added or a shortcut. |
111 | + try: |
112 | + shortcut = shortcut_handler(line) |
113 | + except ShortcutException as e: |
114 | + print(e) |
115 | + sys.exit(1) |
116 | + |
117 | + # display more information about the shortcut / ppa info |
118 | + if not options.assume_yes and shortcut.should_confirm(): |
119 | + try: |
120 | + info = shortcut.info() |
121 | + except ShortcutException as e: |
122 | + print(e) |
123 | + sys.exit(1) |
124 | + |
125 | + print(" %s" % (info["description"] or "")) |
126 | + print(_(" More info: %s") % str(info["web_link"])) |
127 | + if (sys.stdin.isatty() and |
128 | + not "FORCE_ADD_APT_REPOSITORY" in os.environ): |
129 | + if options.remove: |
130 | + print(_("Press [ENTER] to continue or ctrl-c to cancel removing it")) |
131 | + else: |
132 | + print(_("Press [ENTER] to continue or ctrl-c to cancel adding it")) |
133 | + sys.stdin.readline() |
134 | + |
135 | + |
136 | if options.remove: |
137 | - (line, file) = expand_ppa_line(line.strip(), sp.distro.codename) |
138 | + try: |
139 | + (line, file) = shortcut.expand(sp.distro.codename) |
140 | + except ShortcutException as e: |
141 | + print(e) |
142 | + sys.exit(1) |
143 | deb_line = sp.expand_http_line(line) |
144 | debsrc_line = 'deb-src' + deb_line[3:] |
145 | deb_entry = SourceEntry(deb_line, file) |
146 | @@ -204,7 +159,12 @@ |
147 | print(_("Error: '%s' doesn't exist in a sourcelist file") % debsrc_line) |
148 | |
149 | else: |
150 | - if not sp.add_source_from_line(line, options.enable_source): |
151 | - print(_("Error: '%s' invalid") % line) |
152 | + try: |
153 | + if not sp.add_source_from_shortcut(shortcut, options.enable_source): |
154 | + print(_("Error: '%s' invalid") % line) |
155 | + sys.exit(1) |
156 | + except ShortcutException as e: |
157 | + print(e) |
158 | sys.exit(1) |
159 | + |
160 | sp.sourceslist.save() |
161 | |
162 | === modified file 'debian/changelog' |
163 | --- debian/changelog 2013-09-18 17:55:08 +0000 |
164 | +++ debian/changelog 2013-10-05 13:08:15 +0000 |
165 | @@ -1,3 +1,10 @@ |
166 | +software-properties (0.92.26ubuntu1) UNRELEASED; urgency=low |
167 | + |
168 | + * support adding cloud-archive repositories using syntax like |
169 | + cloud-archive:havana (LP: #1233486) |
170 | + |
171 | + -- Scott Moser <smoser@ubuntu.com> Sat, 05 Oct 2013 09:06:25 -0400 |
172 | + |
173 | software-properties (0.92.26) saucy; urgency=low |
174 | |
175 | * SECURITY UPDATE: possible privilege escalation via policykit UID lookup |
176 | |
177 | === modified file 'softwareproperties/SoftwareProperties.py' |
178 | --- softwareproperties/SoftwareProperties.py 2013-06-19 19:48:08 +0000 |
179 | +++ softwareproperties/SoftwareProperties.py 2013-10-05 13:08:15 +0000 |
180 | @@ -24,7 +24,6 @@ |
181 | |
182 | from __future__ import absolute_import, print_function |
183 | |
184 | -import apt |
185 | import apt_pkg |
186 | import copy |
187 | from hashlib import md5 |
188 | @@ -48,10 +47,6 @@ |
189 | except ImportError: |
190 | from ConfigParser import ConfigParser |
191 | from gettext import gettext as _ |
192 | -try: |
193 | - from urllib.parse import urlparse |
194 | -except ImportError: |
195 | - from urlparse import urlparse |
196 | |
197 | import aptsources |
198 | import aptsources.distro |
199 | @@ -59,7 +54,16 @@ |
200 | |
201 | from .AptAuth import AptAuth |
202 | from aptsources.sourceslist import SourcesList, SourceEntry |
203 | -from .ppa import AddPPASigningKeyThread, expand_ppa_line |
204 | +from . import shortcuts |
205 | +from . import ppa |
206 | +from . import cloudarchive |
207 | + |
208 | +_SHORTCUT_FACTORIES = [ |
209 | + ppa.shortcut_handler, |
210 | + cloudarchive.shortcut_handler, |
211 | + shortcuts.shortcut_handler, |
212 | +] |
213 | + |
214 | |
215 | class SoftwareProperties(object): |
216 | |
217 | @@ -650,7 +654,7 @@ |
218 | helper that checks if a given line is in the source list |
219 | return the channel name or None if not found |
220 | """ |
221 | - srcentry = SourceEntry(srcline) |
222 | + srcentry = SourceEntry(srcline) |
223 | if os.path.exists(self.CHANNEL_PATH): |
224 | for f in glob.glob("%s/*.list" % self.CHANNEL_PATH): |
225 | for line in open(f): |
226 | @@ -661,23 +665,30 @@ |
227 | return None |
228 | |
229 | def check_and_add_key_for_whitelisted_channels(self, srcline): |
230 | + # This is maintained for any legacy callers |
231 | + return self.check_and_add_key_for_whitelisted_shortcut(shortcut_handler(srcline)) |
232 | + |
233 | + def check_and_add_key_for_whitelisted_shortcut(self, shortcut): |
234 | """ |
235 | helper that adds the gpg key of the channel to the apt |
236 | keyring *if* the channel is in the whitelist |
237 | /usr/share/app-install/channels or it is a public Launchpad PPA. |
238 | """ |
239 | + (srcline, _fname) = shortcut.expand(codename=self.distro.codename) |
240 | channel = self._is_line_in_whitelisted_channel(srcline) |
241 | if channel: |
242 | keyp = "%s/%s.key" % (self.CHANNEL_PATH, channel) |
243 | self.add_key(keyp) |
244 | - # FIXME: abstract this all alway into the ppa.py file |
245 | - parsed_uri = urlparse(SourceEntry(srcline).uri) |
246 | - if parsed_uri.netloc == 'ppa.launchpad.net': |
247 | - worker = AddPPASigningKeyThread(parsed_uri.path, self.options and self.options.keyserver) |
248 | - worker.start() |
249 | - return worker |
250 | - else: |
251 | - return None |
252 | + |
253 | + cdata = (shortcut.add_key, {'keyserver': (self.options and |
254 | + self.options.keyserver)}) |
255 | + def addkey_func(): |
256 | + func, kwargs = cdata |
257 | + func(**kwargs) |
258 | + |
259 | + worker = threading.Thread(target=addkey_func) |
260 | + worker.start() |
261 | + return worker |
262 | |
263 | def update_interface(self): |
264 | " abstract interface to keep the UI alive " |
265 | @@ -701,10 +712,18 @@ |
266 | |
267 | def add_source_from_line(self, line, enable_source_code=False): |
268 | """ |
269 | - Add a source with the given apt line and auto-add |
270 | - signing key if we have it in the whitelist |
271 | - """ |
272 | - (deb_line, file) = expand_ppa_line(line.strip(), self.distro.codename) |
273 | + Add a source for the given line. |
274 | + """ |
275 | + return self.add_source_from_shortcut(shortcut=shortcut_handler(line), |
276 | + enable_source_code=enable_source_code) |
277 | + |
278 | + def add_source_from_shortcut(self, shortcut, enable_source_code=False): |
279 | + """ |
280 | + Add a source with the given shortcut and add the signing key if the |
281 | + site is in whitelist or the shortcut implementer adds it. |
282 | + """ |
283 | + |
284 | + (deb_line, file) = shortcut.expand(codename=self.distro.codename) |
285 | deb_line = self.expand_http_line(deb_line) |
286 | debsrc_entry_type = 'deb-src' if enable_source_code else '# deb-src' |
287 | debsrc_line = debsrc_entry_type + deb_line[3:] |
288 | @@ -712,7 +731,7 @@ |
289 | new_debsrc_entry = SourceEntry(debsrc_line, file) |
290 | if new_deb_entry.invalid or new_debsrc_entry.invalid: |
291 | return False |
292 | - worker = self.check_and_add_key_for_whitelisted_channels(deb_line) |
293 | + worker = self.check_and_add_key_for_whitelisted_shortcut(shortcut) |
294 | self.sourceslist.add(new_deb_entry.type, |
295 | new_deb_entry.uri, |
296 | new_deb_entry.dist, |
297 | @@ -812,6 +831,15 @@ |
298 | return False |
299 | |
300 | |
301 | +def shortcut_handler(shortcut): |
302 | + for factory in _SHORTCUT_FACTORIES: |
303 | + ret = factory(shortcut) |
304 | + if ret is not None: |
305 | + return ret |
306 | + |
307 | + raise shortcuts.ShortcutException("Unable to handle input '%s'" % shortcut) |
308 | + |
309 | + |
310 | if __name__ == "__main__": |
311 | sp = SoftwareProperties() |
312 | print(sp.get_release_upgrades_policy()) |
313 | |
314 | === added file 'softwareproperties/cloudarchive.py' |
315 | --- softwareproperties/cloudarchive.py 1970-01-01 00:00:00 +0000 |
316 | +++ softwareproperties/cloudarchive.py 2013-10-05 13:08:15 +0000 |
317 | @@ -0,0 +1,123 @@ |
318 | +# software-properties cloud-archive support |
319 | +# |
320 | +# Copyright (c) 2013 Canonical Ltd. |
321 | +# |
322 | +# Author: Scott Moser <smoser@ubuntu.org> |
323 | +# |
324 | +# This program is free software; you can redistribute it and/or |
325 | +# modify it under the terms of the GNU General Public License as |
326 | +# published by the Free Software Foundation; either version 2 of the |
327 | +# License, or (at your option) any later version. |
328 | +# |
329 | +# This program is distributed in the hope that it will be useful, |
330 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
331 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
332 | +# GNU General Public License for more details. |
333 | +# |
334 | +# You should have received a copy of the GNU General Public License |
335 | +# along with this program; if not, write to the Free Software |
336 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
337 | +# USA |
338 | + |
339 | +from __future__ import print_function |
340 | + |
341 | +import apt_pkg |
342 | +import os |
343 | +import subprocess |
344 | +from gettext import gettext as _ |
345 | + |
346 | +from softwareproperties.shortcuts import ShortcutException |
347 | + |
348 | +CODENAME = "precise" |
349 | +OS_RELEASES = ('folsom', 'grizzly', 'havana', 'icehouse') |
350 | +MIRROR = "http://ubuntu-cloud.archive.canonical.com/ubuntu" |
351 | +UCA = "Ubuntu Cloud Archive" |
352 | +WEB_LINK = 'https://wiki.ubuntu.com/ServerTeam/CloudArchive' |
353 | +APT_INSTALL_KEY = ['apt-get', '--quiet', '--assume-yes', 'install', |
354 | + 'ubuntu-cloud-keyring'] |
355 | + |
356 | +ALIASES = {'tools-updates': 'tools'} |
357 | +for _r in OS_RELEASES: |
358 | + ALIASES["%s-updates" % _r] = _r |
359 | + |
360 | +MAP = { |
361 | + 'tools': { |
362 | + 'sldfmt': '%(codename)s-updates/cloud-tools', |
363 | + 'description': UCA + " for cloud-tools (JuJu and MAAS)"}, |
364 | + 'tools-proposed': { |
365 | + 'sldfmt': '%(codename)s-proposed/cloud-tools', |
366 | + 'description': UCA + " for cloud-tools (JuJu and MAAS) [proposed]"} |
367 | +} |
368 | + |
369 | +for _r in OS_RELEASES: |
370 | + MAP[_r] = { |
371 | + 'sldfmt': '%(codename)s-updates/' + _r, |
372 | + 'description': UCA + ' for ' + 'Openstack ' + _r} |
373 | + MAP[_r + "-proposed"] = { |
374 | + 'sldfmt': '%(codename)s-proposed/' + _r, |
375 | + 'description': UCA + ' for ' + 'Openstack %s [proposed]' % _r} |
376 | + |
377 | + |
378 | +class CloudArchiveShortcutHandler(object): |
379 | + def __init__(self, shortcut): |
380 | + self.shortcut = shortcut |
381 | + |
382 | + prefix = "cloud-archive:" |
383 | + |
384 | + subs = {'shortcut': shortcut, 'prefix': prefix, |
385 | + 'ca_names': sorted(MAP.keys())} |
386 | + if not shortcut.startswith(prefix): |
387 | + raise ValueError( |
388 | + _("shortcut '%(shortcut)s' did not start with '%(prefix)s'") |
389 | + % subs) |
390 | + |
391 | + name_in = shortcut[len(prefix):] |
392 | + caname = ALIASES.get(name_in, name_in) |
393 | + |
394 | + subs.update({'input_name': name_in}) |
395 | + if caname not in MAP: |
396 | + raise ShortcutException( |
397 | + _("'%(input_name)s': not a valid cloud-archive name.\n" |
398 | + "Must be one of %(ca_names)s") % subs) |
399 | + |
400 | + self.caname = caname |
401 | + self._info = MAP[caname].copy() |
402 | + self._info['web_link' ] = WEB_LINK |
403 | + |
404 | + def info(self): |
405 | + return self._info |
406 | + |
407 | + def expand(self, codename): |
408 | + if codename not in (CODENAME, os.environ.get("CA_ALLOW_CODENAME")): |
409 | + raise ShortcutException( |
410 | + _("cloud-archive only supported on %(codename)s") |
411 | + % {'codename': CODENAME}) |
412 | + dist = MAP[self.caname]['sldfmt'] % {'codename': codename} |
413 | + line = ' '.join(('deb', MIRROR, dist, 'main',)) |
414 | + return (line, _fname_for_caname(self.caname)) |
415 | + |
416 | + def should_confirm(self): |
417 | + return True |
418 | + |
419 | + def add_key(self, keyserver=None): |
420 | + env = os.environ.copy() |
421 | + env['DEBIAN_FRONTEND'] = 'noninteractive' |
422 | + try: |
423 | + subprocess.check_call(args=APT_INSTALL_KEY, env=env) |
424 | + except subprocess.CalledProcessError as e: |
425 | + return False |
426 | + return True |
427 | + |
428 | + |
429 | +def _fname_for_caname(caname): |
430 | + # caname is an entry in MAP ('tools' or 'tools-proposed') |
431 | + return os.path.join( |
432 | + apt_pkg.config.find_dir("Dir::Etc::sourceparts"), |
433 | + 'cloudarchive-%s.list' % caname) |
434 | + |
435 | + |
436 | +def shortcut_handler(shortcut): |
437 | + try: |
438 | + return CloudArchiveShortcutHandler(shortcut) |
439 | + except ValueError: |
440 | + return None |
441 | |
442 | === modified file 'softwareproperties/ppa.py' |
443 | --- softwareproperties/ppa.py 2013-05-30 17:49:39 +0000 |
444 | +++ softwareproperties/ppa.py 2013-10-05 13:08:15 +0000 |
445 | @@ -29,17 +29,29 @@ |
446 | import subprocess |
447 | import tempfile |
448 | |
449 | +from aptsources.sourceslist import SourceEntry |
450 | +from gettext import gettext as _ |
451 | from threading import Thread |
452 | |
453 | +from .shortcuts import ShortcutException |
454 | + |
455 | try: |
456 | import urllib.request |
457 | - from urllib.error import URLError |
458 | + from urllib.error import HTTPError, URLError |
459 | import urllib.parse |
460 | from http.client import HTTPException |
461 | NEED_PYCURL = False |
462 | except ImportError: |
463 | NEED_PYCURL = True |
464 | import pycurl |
465 | + HTTPError = pycurl.error |
466 | + |
467 | + |
468 | +try: |
469 | + from urllib.parse import urlparse |
470 | +except ImportError: |
471 | + from urlparse import urlparse |
472 | + |
473 | |
474 | DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/" |
475 | # maintained until 2015 |
476 | @@ -138,20 +150,16 @@ |
477 | return len(signing_key_fingerprint) >= 160/8 |
478 | |
479 | |
480 | -class AddPPASigningKeyThread(Thread): |
481 | +class AddPPASigningKey(object): |
482 | " thread class for adding the signing key in the background " |
483 | |
484 | GPG_DEFAULT_OPTIONS = ["gpg", "--no-default-keyring", "--no-options"] |
485 | |
486 | def __init__(self, ppa_path, keyserver=None): |
487 | - Thread.__init__(self) |
488 | self.ppa_path = ppa_path |
489 | self.keyserver = (keyserver if keyserver is not None |
490 | else DEFAULT_KEYSERVER) |
491 | |
492 | - def run(self): |
493 | - self.add_ppa_signing_key(self.ppa_path) |
494 | - |
495 | def _recv_key(self, keyring, secret_keyring, signing_key_fingerprint, keyring_dir): |
496 | try: |
497 | # double check that the signing key is a v4 fingerprint (160bit) |
498 | @@ -211,12 +219,15 @@ |
499 | return False |
500 | return True |
501 | |
502 | - def add_ppa_signing_key(self, ppa_path): |
503 | + def add_ppa_signing_key(self, ppa_path=None): |
504 | """Query and add the corresponding PPA signing key. |
505 | |
506 | The signing key fingerprint is obtained from the Launchpad PPA page, |
507 | via a secure channel, so it can be trusted. |
508 | """ |
509 | + if ppa_path is None: |
510 | + ppa_path = self.ppa_path |
511 | + |
512 | def cleanup(tmpdir): |
513 | shutil.rmtree(tmp_keyring_dir) |
514 | owner_name, ppa_name, distro = ppa_path[1:].split('/') |
515 | @@ -261,6 +272,110 @@ |
516 | return (res == 0) |
517 | |
518 | |
519 | +class AddPPASigningKeyThread(Thread, AddPPASigningKey): |
520 | + # This class is legacy. There are no users inside the software-properties |
521 | + # codebase other than a test case. It was left in case there were outside |
522 | + # users. Internally, we've changed from having a class implement the |
523 | + # tread to explicitly launching a thread and invoking a method in it |
524 | + # see check_and_add_key_for_whitelisted_shortcut for how. |
525 | + def __init__(self, ppa_path, keyserver=None): |
526 | + Thread.__init__(self) |
527 | + AddPPASigningKey.__init__(self, ppa_path=ppa_path, keyserver=keyserver) |
528 | + |
529 | + def run(self): |
530 | + self.add_ppa_signing_key(self.ppa_path) |
531 | + |
532 | + |
533 | +def _get_suggested_ppa_message(user): |
534 | + try: |
535 | + msg = [] |
536 | + from launchpadlib.launchpad import Launchpad |
537 | + lp = Launchpad.login_anonymously(lp_application_name, "production") |
538 | + try: |
539 | + user_inst = lp.people[user] |
540 | + entity_name = _("team") if user_inst.is_team else _("user") |
541 | + if len(user_inst.ppas) > 0: |
542 | + # Translators: %(entity)s is either "team" or "user" |
543 | + msg.append(_("The %(entity)s named '%(user)s' has no PPA named '%(ppa)s'") % { |
544 | + 'entity' : entity_name, |
545 | + 'user' : user, |
546 | + 'ppa' : ppa_name}) |
547 | + msg.append(_("Please choose from the following available PPAs:")) |
548 | + for ppa in user_inst.ppas: |
549 | + msg.append(_(" * '%(name)s': %(displayname)s") % { |
550 | + 'name' : ppa.name, |
551 | + 'displayname' : ppa.displayname}) |
552 | + else: |
553 | + # Translators: %(entity)s is either "team" or "user" |
554 | + msg.append(_("The %(entity)s named '%(user)s' does not have any PPA") % { |
555 | + 'entity' : entity_name, 'user' : user}) |
556 | + return '\n'.join(msg) |
557 | + except KeyError: |
558 | + return '' |
559 | + except ImportError: |
560 | + return _("Please check that the PPA name or format is correct.") |
561 | + |
562 | + |
563 | +def get_ppa_info(shortcut): |
564 | + user, sep, ppa_name = shortcut.split(":")[1].partition("/") |
565 | + ppa_name = ppa_name or "ppa" |
566 | + |
567 | + try: |
568 | + ret = get_ppa_info_from_lp(user, ppa_name) |
569 | + return ret |
570 | + except (HTTPError, Exception) as e: |
571 | + msg = [] |
572 | + msg.append(_("Cannot add PPA: '%s'.") % shortcut) |
573 | + if user.startswith("~"): |
574 | + msg.append((_("Did you mean 'ppa:%s/%s' ?") %(user[1:], ppa_name))) |
575 | + raise ShortcutException('\n'.join(msg) + "\n") |
576 | + |
577 | + # If the PPA does not exist, then try to find if the user/team |
578 | + # exists. If it exists, list down the PPAs |
579 | + raise ShortcutException('\n'.join(msg) + "\n" + |
580 | + _get_suggested_ppa_message(user)) |
581 | + |
582 | + except (ValueError, PPAException): |
583 | + raise ShortcutException( |
584 | + _("Cannot access PPA (%s) to get PPA information, " |
585 | + "please check your internet connection.") % \ |
586 | + (LAUNCHPAD_PPA_API % (user, ppa_name))) |
587 | + |
588 | + |
589 | +class PPAShortcutHandler(object): |
590 | + def __init__(self, shortcut): |
591 | + super(PPAShortcutHandler, self).__init__() |
592 | + info = get_ppa_info(shortcut) |
593 | + |
594 | + if "private" in info and info["private"]: |
595 | + raise ShortcutException( |
596 | + _("Adding private PPAs is not supported currently")) |
597 | + |
598 | + self._info = info |
599 | + self.shortcut = shortcut |
600 | + |
601 | + def info(self): |
602 | + return self._info |
603 | + |
604 | + def expand(self, codename): |
605 | + return expand_ppa_line(self.shortcut, codename) |
606 | + |
607 | + def should_confirm(self): |
608 | + return True |
609 | + |
610 | + def add_key(self, keyserver=None): |
611 | + (srcline, _fname) = self.expand("PPA_SCH_CODENAME") |
612 | + ppa_path = urlparse(SourceEntry(srcline).uri).path |
613 | + apsk = AddPPASigningKey(ppa_path, keyserver=keyserver) |
614 | + return apsk.add_ppa_signing_key() |
615 | + |
616 | + |
617 | +def shortcut_handler(shortcut): |
618 | + if not shortcut.startswith("ppa:"): |
619 | + return None |
620 | + return PPAShortcutHandler(shortcut) |
621 | + |
622 | + |
623 | if __name__ == "__main__": |
624 | import sys |
625 | owner_name, ppa_name = sys.argv[1].split(":")[1].split("/") |
626 | |
627 | === added file 'softwareproperties/shortcuts.py' |
628 | --- softwareproperties/shortcuts.py 1970-01-01 00:00:00 +0000 |
629 | +++ softwareproperties/shortcuts.py 2013-10-05 13:08:15 +0000 |
630 | @@ -0,0 +1,57 @@ |
631 | +# Copyright (c) 2013 Canonical Ltd. |
632 | +# |
633 | +# Author: Scott Moser <smoser@ubuntu.com> |
634 | +# |
635 | +# This program is free software; you can redistribute it and/or |
636 | +# modify it under the terms of the GNU General Public License as |
637 | +# published by the Free Software Foundation; either version 2 of the |
638 | +# License, or (at your option) any later version. |
639 | +# |
640 | +# This program is distributed in the hope that it will be useful, |
641 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
642 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
643 | +# GNU General Public License for more details. |
644 | +# |
645 | +# You should have received a copy of the GNU General Public License |
646 | +# along with this program; if not, write to the Free Software |
647 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
648 | +# USA |
649 | + |
650 | +import aptsources.distro |
651 | +from gettext import gettext as _ |
652 | + |
653 | +_DEF_CODENAME = aptsources.distro.get_distro().codename |
654 | + |
655 | + |
656 | +class ShortcutHandler(object): |
657 | + # the defeault ShortcutHandler only handles actual apt lines. |
658 | + # ie, 'shortcut' here is a line like you'd find in /etc/apt/sources.list: |
659 | + # deb MIRROR RELEASE-POCKET COMPONENT |
660 | + def __init__(self, shortcut): |
661 | + self.shortcut = shortcut |
662 | + |
663 | + def add_key(self, keyserver=None): |
664 | + return None |
665 | + |
666 | + def expand(self, codename=None): |
667 | + return (self.shortcut, None) |
668 | + |
669 | + def info(self): |
670 | + return { |
671 | + 'description': _("No description available for '%(shortcut)s'") % |
672 | + {'shortcut': self.shortcut}, |
673 | + 'web_link': _("web link unavailable")} |
674 | + |
675 | + def should_confirm(self): |
676 | + return False |
677 | + |
678 | + |
679 | +class ShortcutException(Exception): |
680 | + pass |
681 | + |
682 | + |
683 | +def shortcut_handler(shortcut): |
684 | + # this is the default shortcut handler, so it matches anything |
685 | + return ShortcutHandler(shortcut) |
686 | + |
687 | +# vi: ts=4 expandtab |
On Tue, Oct 01, 2013 at 03:17:34AM -0000, Scott Moser wrote: archive: <item>' where item is one of 'tools{ ,-updates, proposed} ', or '<openstack- release> {,-updates, -proposed} .
> The proposal to merge lp:~smoser/software-properties/shortcut-refactor into lp:software-properties has been updated.
>
> Commit Message changed to:
>
> Refactor ppa handling into generic 'shortcuts', add handler for cloud-archive
>
> This re-factors the 'ppa:' handling into something more generic.
> Then, we add a handler for 'cloud-
>
> fixes bug 1233486
Thanks for this branch! I looked over the code and it looks good,
thanks for the refactor, this made the concept nicer and more
generic. But it seems like the branch is breaking the tests, i.e. when
I run "python setup.py test" on my box I get some test failures.
Cheers,
Michael