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
=== modified file 'add-apt-repository'
--- add-apt-repository 2013-05-28 19:52:17 +0000
+++ add-apt-repository 2013-10-05 13:08:15 +0000
@@ -8,50 +8,14 @@
8import gettext8import gettext
9import locale9import locale
1010
11from softwareproperties.SoftwareProperties import SoftwareProperties11from softwareproperties.SoftwareProperties import SoftwareProperties, shortcut_handler
12from softwareproperties.ppa import DEFAULT_KEYSERVER, expand_ppa_line12from softwareproperties.shortcuts import ShortcutException
13from softwareproperties import lp_application_name13from softwareproperties.ppa import DEFAULT_KEYSERVER
14import aptsources14import aptsources
15from aptsources.sourceslist import SourceEntry15from aptsources.sourceslist import SourceEntry
16from aptsources.distro import *
17from optparse import OptionParser16from optparse import OptionParser
18from gettext import gettext as _17from gettext import gettext as _
1918
20try:
21 from urllib.error import HTTPError, URLError
22except ImportError:
23 import pycurl
24 HTTPError = pycurl.error
25
26def _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
55if __name__ == "__main__":19if __name__ == "__main__":
56 # Force encoding to UTF-8 even in non-UTF-8 locales.20 # Force encoding to UTF-8 even in non-UTF-8 locales.
57 sys.stdout = io.TextIOWrapper(21 sys.stdout = io.TextIOWrapper(
@@ -123,46 +87,6 @@
123 # get the line87 # get the line
124 line = args[0]88 line = args[0]
12589
126 # display PPA info (if needed)
127 if line.startswith("ppa:") and not options.assume_yes:
128 from softwareproperties.ppa import PPAException, get_ppa_info_from_lp, LAUNCHPAD_PPA_API
129 user, sep, ppa_name = line.split(":")[1].partition("/")
130 ppa_name = ppa_name or "ppa"
131 try:
132 ppa_info = get_ppa_info_from_lp(user, ppa_name)
133 except HTTPError:
134 print(_("Cannot add PPA: '%s'.") % line)
135 if user.startswith("~"):
136 print(_("Did you mean 'ppa:%s/%s' ?") %(user[1:], ppa_name))
137 sys.exit(1) # Exit because the user cannot be correct
138 # If the PPA does not exist, then try to find if the user/team
139 # exists. If it exists, list down the PPAs
140 _maybe_suggest_ppa_name_based_on_user(user)
141 sys.exit(1)
142 except (ValueError, PPAException):
143 print(_("Cannot access PPA (%s) to get PPA information, "
144 "please check your internet connection.") % \
145 (LAUNCHPAD_PPA_API % (user, ppa_name)))
146 sys.exit(1)
147 # private PPAs are not supported
148 if "private" in ppa_info and ppa_info["private"]:
149 print(_("Adding private PPAs is not supported currently"))
150 sys.exit(1)
151
152 if options.remove:
153 print(_("You are about to remove the following PPA from your system:"))
154 else:
155 print(_("You are about to add the following PPA to your system:"))
156 print(" %s" % (ppa_info["description"] or ""))
157 print(_(" More info: %s") % str(ppa_info["web_link"]))
158 if (sys.stdin.isatty() and
159 not "FORCE_ADD_APT_REPOSITORY" in os.environ):
160 if options.remove:
161 print(_("Press [ENTER] to continue or ctrl-c to cancel removing it"))
162 else:
163 print(_("Press [ENTER] to continue or ctrl-c to cancel adding it"))
164 sys.stdin.readline()
165
166 # add it90 # add it
167 sp = SoftwareProperties(options=options)91 sp = SoftwareProperties(options=options)
168 distro = aptsources.distro.get_distro()92 distro = aptsources.distro.get_distro()
@@ -188,8 +112,39 @@
188 sp.sourceslist.save()112 sp.sourceslist.save()
189 sys.exit(0)113 sys.exit(0)
190114
115 # this wasn't a component name ('multiverse', 'backports'), so its either
116 # a actual line to be added or a shortcut.
117 try:
118 shortcut = shortcut_handler(line)
119 except ShortcutException as e:
120 print(e)
121 sys.exit(1)
122
123 # display more information about the shortcut / ppa info
124 if not options.assume_yes and shortcut.should_confirm():
125 try:
126 info = shortcut.info()
127 except ShortcutException as e:
128 print(e)
129 sys.exit(1)
130
131 print(" %s" % (info["description"] or ""))
132 print(_(" More info: %s") % str(info["web_link"]))
133 if (sys.stdin.isatty() and
134 not "FORCE_ADD_APT_REPOSITORY" in os.environ):
135 if options.remove:
136 print(_("Press [ENTER] to continue or ctrl-c to cancel removing it"))
137 else:
138 print(_("Press [ENTER] to continue or ctrl-c to cancel adding it"))
139 sys.stdin.readline()
140
141
191 if options.remove:142 if options.remove:
192 (line, file) = expand_ppa_line(line.strip(), sp.distro.codename)143 try:
144 (line, file) = shortcut.expand(sp.distro.codename)
145 except ShortcutException as e:
146 print(e)
147 sys.exit(1)
193 deb_line = sp.expand_http_line(line)148 deb_line = sp.expand_http_line(line)
194 debsrc_line = 'deb-src' + deb_line[3:]149 debsrc_line = 'deb-src' + deb_line[3:]
195 deb_entry = SourceEntry(deb_line, file)150 deb_entry = SourceEntry(deb_line, file)
@@ -204,7 +159,12 @@
204 print(_("Error: '%s' doesn't exist in a sourcelist file") % debsrc_line)159 print(_("Error: '%s' doesn't exist in a sourcelist file") % debsrc_line)
205160
206 else:161 else:
207 if not sp.add_source_from_line(line, options.enable_source):162 try:
208 print(_("Error: '%s' invalid") % line)163 if not sp.add_source_from_shortcut(shortcut, options.enable_source):
164 print(_("Error: '%s' invalid") % line)
165 sys.exit(1)
166 except ShortcutException as e:
167 print(e)
209 sys.exit(1)168 sys.exit(1)
169
210 sp.sourceslist.save()170 sp.sourceslist.save()
211171
=== modified file 'debian/changelog'
--- debian/changelog 2013-09-18 17:55:08 +0000
+++ debian/changelog 2013-10-05 13:08:15 +0000
@@ -1,3 +1,10 @@
1software-properties (0.92.26ubuntu1) UNRELEASED; urgency=low
2
3 * support adding cloud-archive repositories using syntax like
4 cloud-archive:havana (LP: #1233486)
5
6 -- Scott Moser <smoser@ubuntu.com> Sat, 05 Oct 2013 09:06:25 -0400
7
1software-properties (0.92.26) saucy; urgency=low8software-properties (0.92.26) saucy; urgency=low
29
3 * SECURITY UPDATE: possible privilege escalation via policykit UID lookup10 * SECURITY UPDATE: possible privilege escalation via policykit UID lookup
411
=== modified file 'softwareproperties/SoftwareProperties.py'
--- softwareproperties/SoftwareProperties.py 2013-06-19 19:48:08 +0000
+++ softwareproperties/SoftwareProperties.py 2013-10-05 13:08:15 +0000
@@ -24,7 +24,6 @@
2424
25from __future__ import absolute_import, print_function25from __future__ import absolute_import, print_function
2626
27import apt
28import apt_pkg27import apt_pkg
29import copy28import copy
30from hashlib import md529from hashlib import md5
@@ -48,10 +47,6 @@
48except ImportError:47except ImportError:
49 from ConfigParser import ConfigParser48 from ConfigParser import ConfigParser
50from gettext import gettext as _49from gettext import gettext as _
51try:
52 from urllib.parse import urlparse
53except ImportError:
54 from urlparse import urlparse
5550
56import aptsources51import aptsources
57import aptsources.distro52import aptsources.distro
@@ -59,7 +54,16 @@
5954
60from .AptAuth import AptAuth55from .AptAuth import AptAuth
61from aptsources.sourceslist import SourcesList, SourceEntry56from aptsources.sourceslist import SourcesList, SourceEntry
62from .ppa import AddPPASigningKeyThread, expand_ppa_line57from . import shortcuts
58from . import ppa
59from . import cloudarchive
60
61_SHORTCUT_FACTORIES = [
62 ppa.shortcut_handler,
63 cloudarchive.shortcut_handler,
64 shortcuts.shortcut_handler,
65]
66
6367
64class SoftwareProperties(object):68class SoftwareProperties(object):
6569
@@ -650,7 +654,7 @@
650 helper that checks if a given line is in the source list654 helper that checks if a given line is in the source list
651 return the channel name or None if not found655 return the channel name or None if not found
652 """656 """
653 srcentry = SourceEntry(srcline) 657 srcentry = SourceEntry(srcline)
654 if os.path.exists(self.CHANNEL_PATH):658 if os.path.exists(self.CHANNEL_PATH):
655 for f in glob.glob("%s/*.list" % self.CHANNEL_PATH):659 for f in glob.glob("%s/*.list" % self.CHANNEL_PATH):
656 for line in open(f):660 for line in open(f):
@@ -661,23 +665,30 @@
661 return None665 return None
662666
663 def check_and_add_key_for_whitelisted_channels(self, srcline):667 def check_and_add_key_for_whitelisted_channels(self, srcline):
668 # This is maintained for any legacy callers
669 return self.check_and_add_key_for_whitelisted_shortcut(shortcut_handler(srcline))
670
671 def check_and_add_key_for_whitelisted_shortcut(self, shortcut):
664 """672 """
665 helper that adds the gpg key of the channel to the apt673 helper that adds the gpg key of the channel to the apt
666 keyring *if* the channel is in the whitelist674 keyring *if* the channel is in the whitelist
667 /usr/share/app-install/channels or it is a public Launchpad PPA.675 /usr/share/app-install/channels or it is a public Launchpad PPA.
668 """676 """
677 (srcline, _fname) = shortcut.expand(codename=self.distro.codename)
669 channel = self._is_line_in_whitelisted_channel(srcline)678 channel = self._is_line_in_whitelisted_channel(srcline)
670 if channel:679 if channel:
671 keyp = "%s/%s.key" % (self.CHANNEL_PATH, channel)680 keyp = "%s/%s.key" % (self.CHANNEL_PATH, channel)
672 self.add_key(keyp)681 self.add_key(keyp)
673 # FIXME: abstract this all alway into the ppa.py file682
674 parsed_uri = urlparse(SourceEntry(srcline).uri)683 cdata = (shortcut.add_key, {'keyserver': (self.options and
675 if parsed_uri.netloc == 'ppa.launchpad.net':684 self.options.keyserver)})
676 worker = AddPPASigningKeyThread(parsed_uri.path, self.options and self.options.keyserver)685 def addkey_func():
677 worker.start()686 func, kwargs = cdata
678 return worker687 func(**kwargs)
679 else:688
680 return None689 worker = threading.Thread(target=addkey_func)
690 worker.start()
691 return worker
681692
682 def update_interface(self):693 def update_interface(self):
683 " abstract interface to keep the UI alive "694 " abstract interface to keep the UI alive "
@@ -701,10 +712,18 @@
701712
702 def add_source_from_line(self, line, enable_source_code=False):713 def add_source_from_line(self, line, enable_source_code=False):
703 """714 """
704 Add a source with the given apt line and auto-add715 Add a source for the given line.
705 signing key if we have it in the whitelist716 """
706 """717 return self.add_source_from_shortcut(shortcut=shortcut_handler(line),
707 (deb_line, file) = expand_ppa_line(line.strip(), self.distro.codename)718 enable_source_code=enable_source_code)
719
720 def add_source_from_shortcut(self, shortcut, enable_source_code=False):
721 """
722 Add a source with the given shortcut and add the signing key if the
723 site is in whitelist or the shortcut implementer adds it.
724 """
725
726 (deb_line, file) = shortcut.expand(codename=self.distro.codename)
708 deb_line = self.expand_http_line(deb_line)727 deb_line = self.expand_http_line(deb_line)
709 debsrc_entry_type = 'deb-src' if enable_source_code else '# deb-src'728 debsrc_entry_type = 'deb-src' if enable_source_code else '# deb-src'
710 debsrc_line = debsrc_entry_type + deb_line[3:]729 debsrc_line = debsrc_entry_type + deb_line[3:]
@@ -712,7 +731,7 @@
712 new_debsrc_entry = SourceEntry(debsrc_line, file)731 new_debsrc_entry = SourceEntry(debsrc_line, file)
713 if new_deb_entry.invalid or new_debsrc_entry.invalid:732 if new_deb_entry.invalid or new_debsrc_entry.invalid:
714 return False733 return False
715 worker = self.check_and_add_key_for_whitelisted_channels(deb_line)734 worker = self.check_and_add_key_for_whitelisted_shortcut(shortcut)
716 self.sourceslist.add(new_deb_entry.type,735 self.sourceslist.add(new_deb_entry.type,
717 new_deb_entry.uri,736 new_deb_entry.uri,
718 new_deb_entry.dist,737 new_deb_entry.dist,
@@ -812,6 +831,15 @@
812 return False831 return False
813832
814833
834def shortcut_handler(shortcut):
835 for factory in _SHORTCUT_FACTORIES:
836 ret = factory(shortcut)
837 if ret is not None:
838 return ret
839
840 raise shortcuts.ShortcutException("Unable to handle input '%s'" % shortcut)
841
842
815if __name__ == "__main__":843if __name__ == "__main__":
816 sp = SoftwareProperties()844 sp = SoftwareProperties()
817 print(sp.get_release_upgrades_policy())845 print(sp.get_release_upgrades_policy())
818846
=== added file 'softwareproperties/cloudarchive.py'
--- softwareproperties/cloudarchive.py 1970-01-01 00:00:00 +0000
+++ softwareproperties/cloudarchive.py 2013-10-05 13:08:15 +0000
@@ -0,0 +1,123 @@
1# software-properties cloud-archive support
2#
3# Copyright (c) 2013 Canonical Ltd.
4#
5# Author: Scott Moser <smoser@ubuntu.org>
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA
21
22from __future__ import print_function
23
24import apt_pkg
25import os
26import subprocess
27from gettext import gettext as _
28
29from softwareproperties.shortcuts import ShortcutException
30
31CODENAME = "precise"
32OS_RELEASES = ('folsom', 'grizzly', 'havana', 'icehouse')
33MIRROR = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
34UCA = "Ubuntu Cloud Archive"
35WEB_LINK = 'https://wiki.ubuntu.com/ServerTeam/CloudArchive'
36APT_INSTALL_KEY = ['apt-get', '--quiet', '--assume-yes', 'install',
37 'ubuntu-cloud-keyring']
38
39ALIASES = {'tools-updates': 'tools'}
40for _r in OS_RELEASES:
41 ALIASES["%s-updates" % _r] = _r
42
43MAP = {
44 'tools': {
45 'sldfmt': '%(codename)s-updates/cloud-tools',
46 'description': UCA + " for cloud-tools (JuJu and MAAS)"},
47 'tools-proposed': {
48 'sldfmt': '%(codename)s-proposed/cloud-tools',
49 'description': UCA + " for cloud-tools (JuJu and MAAS) [proposed]"}
50}
51
52for _r in OS_RELEASES:
53 MAP[_r] = {
54 'sldfmt': '%(codename)s-updates/' + _r,
55 'description': UCA + ' for ' + 'Openstack ' + _r}
56 MAP[_r + "-proposed"] = {
57 'sldfmt': '%(codename)s-proposed/' + _r,
58 'description': UCA + ' for ' + 'Openstack %s [proposed]' % _r}
59
60
61class CloudArchiveShortcutHandler(object):
62 def __init__(self, shortcut):
63 self.shortcut = shortcut
64
65 prefix = "cloud-archive:"
66
67 subs = {'shortcut': shortcut, 'prefix': prefix,
68 'ca_names': sorted(MAP.keys())}
69 if not shortcut.startswith(prefix):
70 raise ValueError(
71 _("shortcut '%(shortcut)s' did not start with '%(prefix)s'")
72 % subs)
73
74 name_in = shortcut[len(prefix):]
75 caname = ALIASES.get(name_in, name_in)
76
77 subs.update({'input_name': name_in})
78 if caname not in MAP:
79 raise ShortcutException(
80 _("'%(input_name)s': not a valid cloud-archive name.\n"
81 "Must be one of %(ca_names)s") % subs)
82
83 self.caname = caname
84 self._info = MAP[caname].copy()
85 self._info['web_link' ] = WEB_LINK
86
87 def info(self):
88 return self._info
89
90 def expand(self, codename):
91 if codename not in (CODENAME, os.environ.get("CA_ALLOW_CODENAME")):
92 raise ShortcutException(
93 _("cloud-archive only supported on %(codename)s")
94 % {'codename': CODENAME})
95 dist = MAP[self.caname]['sldfmt'] % {'codename': codename}
96 line = ' '.join(('deb', MIRROR, dist, 'main',))
97 return (line, _fname_for_caname(self.caname))
98
99 def should_confirm(self):
100 return True
101
102 def add_key(self, keyserver=None):
103 env = os.environ.copy()
104 env['DEBIAN_FRONTEND'] = 'noninteractive'
105 try:
106 subprocess.check_call(args=APT_INSTALL_KEY, env=env)
107 except subprocess.CalledProcessError as e:
108 return False
109 return True
110
111
112def _fname_for_caname(caname):
113 # caname is an entry in MAP ('tools' or 'tools-proposed')
114 return os.path.join(
115 apt_pkg.config.find_dir("Dir::Etc::sourceparts"),
116 'cloudarchive-%s.list' % caname)
117
118
119def shortcut_handler(shortcut):
120 try:
121 return CloudArchiveShortcutHandler(shortcut)
122 except ValueError:
123 return None
0124
=== modified file 'softwareproperties/ppa.py'
--- softwareproperties/ppa.py 2013-05-30 17:49:39 +0000
+++ softwareproperties/ppa.py 2013-10-05 13:08:15 +0000
@@ -29,17 +29,29 @@
29import subprocess29import subprocess
30import tempfile30import tempfile
3131
32from aptsources.sourceslist import SourceEntry
33from gettext import gettext as _
32from threading import Thread34from threading import Thread
3335
36from .shortcuts import ShortcutException
37
34try:38try:
35 import urllib.request39 import urllib.request
36 from urllib.error import URLError40 from urllib.error import HTTPError, URLError
37 import urllib.parse41 import urllib.parse
38 from http.client import HTTPException42 from http.client import HTTPException
39 NEED_PYCURL = False43 NEED_PYCURL = False
40except ImportError:44except ImportError:
41 NEED_PYCURL = True45 NEED_PYCURL = True
42 import pycurl46 import pycurl
47 HTTPError = pycurl.error
48
49
50try:
51 from urllib.parse import urlparse
52except ImportError:
53 from urlparse import urlparse
54
4355
44DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/"56DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/"
45# maintained until 201557# maintained until 2015
@@ -138,20 +150,16 @@
138 return len(signing_key_fingerprint) >= 160/8150 return len(signing_key_fingerprint) >= 160/8
139151
140152
141class AddPPASigningKeyThread(Thread):153class AddPPASigningKey(object):
142 " thread class for adding the signing key in the background "154 " thread class for adding the signing key in the background "
143155
144 GPG_DEFAULT_OPTIONS = ["gpg", "--no-default-keyring", "--no-options"]156 GPG_DEFAULT_OPTIONS = ["gpg", "--no-default-keyring", "--no-options"]
145157
146 def __init__(self, ppa_path, keyserver=None):158 def __init__(self, ppa_path, keyserver=None):
147 Thread.__init__(self)
148 self.ppa_path = ppa_path159 self.ppa_path = ppa_path
149 self.keyserver = (keyserver if keyserver is not None160 self.keyserver = (keyserver if keyserver is not None
150 else DEFAULT_KEYSERVER)161 else DEFAULT_KEYSERVER)
151162
152 def run(self):
153 self.add_ppa_signing_key(self.ppa_path)
154
155 def _recv_key(self, keyring, secret_keyring, signing_key_fingerprint, keyring_dir):163 def _recv_key(self, keyring, secret_keyring, signing_key_fingerprint, keyring_dir):
156 try:164 try:
157 # double check that the signing key is a v4 fingerprint (160bit)165 # double check that the signing key is a v4 fingerprint (160bit)
@@ -211,12 +219,15 @@
211 return False219 return False
212 return True220 return True
213221
214 def add_ppa_signing_key(self, ppa_path):222 def add_ppa_signing_key(self, ppa_path=None):
215 """Query and add the corresponding PPA signing key.223 """Query and add the corresponding PPA signing key.
216 224
217 The signing key fingerprint is obtained from the Launchpad PPA page,225 The signing key fingerprint is obtained from the Launchpad PPA page,
218 via a secure channel, so it can be trusted.226 via a secure channel, so it can be trusted.
219 """227 """
228 if ppa_path is None:
229 ppa_path = self.ppa_path
230
220 def cleanup(tmpdir):231 def cleanup(tmpdir):
221 shutil.rmtree(tmp_keyring_dir)232 shutil.rmtree(tmp_keyring_dir)
222 owner_name, ppa_name, distro = ppa_path[1:].split('/')233 owner_name, ppa_name, distro = ppa_path[1:].split('/')
@@ -261,6 +272,110 @@
261 return (res == 0)272 return (res == 0)
262273
263274
275class AddPPASigningKeyThread(Thread, AddPPASigningKey):
276 # This class is legacy. There are no users inside the software-properties
277 # codebase other than a test case. It was left in case there were outside
278 # users. Internally, we've changed from having a class implement the
279 # tread to explicitly launching a thread and invoking a method in it
280 # see check_and_add_key_for_whitelisted_shortcut for how.
281 def __init__(self, ppa_path, keyserver=None):
282 Thread.__init__(self)
283 AddPPASigningKey.__init__(self, ppa_path=ppa_path, keyserver=keyserver)
284
285 def run(self):
286 self.add_ppa_signing_key(self.ppa_path)
287
288
289def _get_suggested_ppa_message(user):
290 try:
291 msg = []
292 from launchpadlib.launchpad import Launchpad
293 lp = Launchpad.login_anonymously(lp_application_name, "production")
294 try:
295 user_inst = lp.people[user]
296 entity_name = _("team") if user_inst.is_team else _("user")
297 if len(user_inst.ppas) > 0:
298 # Translators: %(entity)s is either "team" or "user"
299 msg.append(_("The %(entity)s named '%(user)s' has no PPA named '%(ppa)s'") % {
300 'entity' : entity_name,
301 'user' : user,
302 'ppa' : ppa_name})
303 msg.append(_("Please choose from the following available PPAs:"))
304 for ppa in user_inst.ppas:
305 msg.append(_(" * '%(name)s': %(displayname)s") % {
306 'name' : ppa.name,
307 'displayname' : ppa.displayname})
308 else:
309 # Translators: %(entity)s is either "team" or "user"
310 msg.append(_("The %(entity)s named '%(user)s' does not have any PPA") % {
311 'entity' : entity_name, 'user' : user})
312 return '\n'.join(msg)
313 except KeyError:
314 return ''
315 except ImportError:
316 return _("Please check that the PPA name or format is correct.")
317
318
319def get_ppa_info(shortcut):
320 user, sep, ppa_name = shortcut.split(":")[1].partition("/")
321 ppa_name = ppa_name or "ppa"
322
323 try:
324 ret = get_ppa_info_from_lp(user, ppa_name)
325 return ret
326 except (HTTPError, Exception) as e:
327 msg = []
328 msg.append(_("Cannot add PPA: '%s'.") % shortcut)
329 if user.startswith("~"):
330 msg.append((_("Did you mean 'ppa:%s/%s' ?") %(user[1:], ppa_name)))
331 raise ShortcutException('\n'.join(msg) + "\n")
332
333 # If the PPA does not exist, then try to find if the user/team
334 # exists. If it exists, list down the PPAs
335 raise ShortcutException('\n'.join(msg) + "\n" +
336 _get_suggested_ppa_message(user))
337
338 except (ValueError, PPAException):
339 raise ShortcutException(
340 _("Cannot access PPA (%s) to get PPA information, "
341 "please check your internet connection.") % \
342 (LAUNCHPAD_PPA_API % (user, ppa_name)))
343
344
345class PPAShortcutHandler(object):
346 def __init__(self, shortcut):
347 super(PPAShortcutHandler, self).__init__()
348 info = get_ppa_info(shortcut)
349
350 if "private" in info and info["private"]:
351 raise ShortcutException(
352 _("Adding private PPAs is not supported currently"))
353
354 self._info = info
355 self.shortcut = shortcut
356
357 def info(self):
358 return self._info
359
360 def expand(self, codename):
361 return expand_ppa_line(self.shortcut, codename)
362
363 def should_confirm(self):
364 return True
365
366 def add_key(self, keyserver=None):
367 (srcline, _fname) = self.expand("PPA_SCH_CODENAME")
368 ppa_path = urlparse(SourceEntry(srcline).uri).path
369 apsk = AddPPASigningKey(ppa_path, keyserver=keyserver)
370 return apsk.add_ppa_signing_key()
371
372
373def shortcut_handler(shortcut):
374 if not shortcut.startswith("ppa:"):
375 return None
376 return PPAShortcutHandler(shortcut)
377
378
264if __name__ == "__main__":379if __name__ == "__main__":
265 import sys380 import sys
266 owner_name, ppa_name = sys.argv[1].split(":")[1].split("/")381 owner_name, ppa_name = sys.argv[1].split(":")[1].split("/")
267382
=== added file 'softwareproperties/shortcuts.py'
--- softwareproperties/shortcuts.py 1970-01-01 00:00:00 +0000
+++ softwareproperties/shortcuts.py 2013-10-05 13:08:15 +0000
@@ -0,0 +1,57 @@
1# Copyright (c) 2013 Canonical Ltd.
2#
3# Author: Scott Moser <smoser@ubuntu.com>
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License as
7# published by the Free Software Foundation; either version 2 of the
8# License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18# USA
19
20import aptsources.distro
21from gettext import gettext as _
22
23_DEF_CODENAME = aptsources.distro.get_distro().codename
24
25
26class ShortcutHandler(object):
27 # the defeault ShortcutHandler only handles actual apt lines.
28 # ie, 'shortcut' here is a line like you'd find in /etc/apt/sources.list:
29 # deb MIRROR RELEASE-POCKET COMPONENT
30 def __init__(self, shortcut):
31 self.shortcut = shortcut
32
33 def add_key(self, keyserver=None):
34 return None
35
36 def expand(self, codename=None):
37 return (self.shortcut, None)
38
39 def info(self):
40 return {
41 'description': _("No description available for '%(shortcut)s'") %
42 {'shortcut': self.shortcut},
43 'web_link': _("web link unavailable")}
44
45 def should_confirm(self):
46 return False
47
48
49class ShortcutException(Exception):
50 pass
51
52
53def shortcut_handler(shortcut):
54 # this is the default shortcut handler, so it matches anything
55 return ShortcutHandler(shortcut)
56
57# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches

to status/vote changes: