Merge lp:~smoser/software-properties/shortcut-refactor into lp:software-properties

Proposed by Scott Moser
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
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-archive:<item>' where item is one of 'tools{,-updates,proposed}', or '<openstack-release>{,-updates,-proposed}.

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.

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

On Tue, Oct 01, 2013 at 03:17:34AM -0000, Scott Moser wrote:
> 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-archive:<item>' where item is one of 'tools{,-updates,proposed}', or '<openstack-release>{,-updates,-proposed}.
>
> 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

Revision history for this message
Scott Moser (smoser) wrote :

I'll fix. thanks.

Revision history for this message
Scott Moser (smoser) wrote :

I've fixed the test case.

881. By Scott Moser

fix test case, make AddPPASigningKeyThread more correct.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches

to status/vote changes: