Merge lp:~pitti/aptdaemon/pkcompat-enhancements into lp:aptdaemon

Proposed by Martin Pitt on 2012-05-31
Status: Merged
Merged at revision: 836
Proposed branch: lp:~pitti/aptdaemon/pkcompat-enhancements
Merge into: lp:aptdaemon
Diff against target: 246 lines (+118/-17)
4 files modified
README.PackageKit (+1/-2)
aptdaemon/pkcompat.py (+21/-4)
aptdaemon/worker.py (+12/-11)
tests/test_pk.py (+84/-0)
To merge this branch: bzr merge lp:~pitti/aptdaemon/pkcompat-enhancements
Reviewer Review Type Date Requested Status
Aptdaemon Developers 2012-05-31 Pending
Review via email: mp+108201@code.launchpad.net

Description of the Change

Implement the InstallSignature() PackageKit API.

This branch also changes the actual aptdaemon implementation in worker.py to
get along with "apt-key" exiting with code 2 even on success (in a separate
commit). This happens for both my own key (26B47B9F) as well as the glatzor.gpg
test key. I have tried with a few other keys as well. If it exits with
non-zero, it now checks stdout for success/failure.

To post a comment you must log in.
Sebastian Heinlein (glatzor) wrote :

See also https://code.launchpad.net/~glatzor/python-apt/auth/+merge/108290

I would like to move the apt-key/gnupg handling to python-apt

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.PackageKit'
2--- README.PackageKit 2012-05-31 13:07:45 +0000
3+++ README.PackageKit 2012-05-31 16:44:20 +0000
4@@ -52,6 +52,7 @@
5 - UpgradeSystem
6 - WhatProvides (no builtin handling, only plugins)
7 - RepoEnable: (only enabling, not disabling)
8+ - InstallSignature
9
10 Not yet supported roles:
11
12@@ -62,8 +63,6 @@
13 - InstallFiles: not implemented in the worker
14 - SimulateInstallFiles: not implemented in the worker
15 sessioninstaller.
16- - InstallSignature: not implemented in the worker. Could be mapped to
17- AddVendorKeyFromFile.
18
19 Roles we won't support at all because of missing infrastructure in APT:
20
21
22=== modified file 'aptdaemon/pkcompat.py'
23--- aptdaemon/pkcompat.py 2012-05-31 14:53:14 +0000
24+++ aptdaemon/pkcompat.py 2012-05-31 16:44:20 +0000
25@@ -263,6 +263,7 @@
26 pk_enums.ROLE_SEARCH_FILE,
27 pk_enums.ROLE_WHAT_PROVIDES,
28 pk_enums.ROLE_REPO_ENABLE,
29+ pk_enums.ROLE_INSTALL_SIGNATURE,
30 pk_enums.ROLE_DOWNLOAD_PACKAGES]
31 if META_RELEASE_SUPPORT:
32 SUPPORTED_ROLES.append(pk_enums.ROLE_GET_DISTRO_UPGRADES)
33@@ -1746,9 +1747,9 @@
34 self.role = pk_enums.ROLE_INSTALL_FILES
35 GObject.idle_add(self._fail_not_implemented)
36
37- @dbus.service.method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
38- in_signature="sss", out_signature="",
39- sender_keyword="sender")
40+ @dbus_deferred_method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
41+ in_signature="sss", out_signature="",
42+ sender_keyword="sender")
43 def InstallSignature(self, sig_type, key_id, package_id, sender):
44 """This method allows us to install new security keys.
45
46@@ -1756,9 +1757,13 @@
47 :param key_id: A key ID, e.g. BB7576AC
48 :param package_id:
49 A PackageID for the package that the user is trying to install
50+ (ignored)
51 """
52 self.role = pk_enums.ROLE_INSTALL_SIGNATURE
53- GObject.idle_add(self._fail_not_implemented)
54+ kwargs = {"sig_type": sig_type,
55+ "key_id": key_id,
56+ "package_id": package_id}
57+ return self._run_query(kwargs, sender)
58
59 @dbus.service.method(PACKAGEKIT_TRANS_DBUS_INTERFACE,
60 in_signature="sss", out_signature="",
61@@ -1987,6 +1992,8 @@
62 self.what_provides(trans, **trans.kwargs)
63 elif trans.pktrans.role == pk_enums.ROLE_REPO_ENABLE:
64 self.repo_enable(trans, **trans.kwargs)
65+ elif trans.pktrans.role == pk_enums.ROLE_INSTALL_SIGNATURE:
66+ self.install_signature(trans, **trans.kwargs)
67 else:
68 raise TransactionFailed(aptd_enums.ERROR_UNKNOWN,
69 "Role %s isn't supported",
70@@ -2598,6 +2605,16 @@
71 self.add_repository(trans, fields[0], fields[1], fields[2],
72 fields[3:], '', None)
73
74+ def install_signature(self, trans, sig_type, key_id, package_id):
75+ """Install an archive key."""
76+
77+ if sig_type != 'gpg':
78+ raise TransactionFailed(aptd_enums.ERROR_NOT_SUPPORTED,
79+ "type %s is not supported" % sig_type)
80+
81+ keyserver = os.environ.get('APTDAEMON_KEYSERVER',
82+ 'hkp://keyserver.ubuntu.com:80')
83+ self.add_vendor_key_from_keyserver(trans, key_id, keyserver)
84
85 # Helpers
86
87
88=== modified file 'aptdaemon/worker.py'
89--- aptdaemon/worker.py 2012-05-09 00:50:29 +0000
90+++ aptdaemon/worker.py 2012-05-31 16:44:20 +0000
91@@ -514,10 +514,13 @@
92 #FIXME: Use GObject.spawn_async and deferreds in the worker
93 # Alternatively we could use python-pyme directly for a better
94 # error handling. Or the --status-fd of gpg
95+ env = os.environ.copy()
96+ env['LC_MESSAGES'] = 'C'
97 proc = subprocess.Popen(["/usr/bin/apt-key", "adv",
98 "--keyserver", keyserver,
99 "--recv", keyid], stderr=subprocess.STDOUT,
100- stdout=subprocess.PIPE, close_fds=True)
101+ stdout=subprocess.PIPE, close_fds=True,
102+ env=env)
103 while proc.poll() is None:
104 self._iterate_mainloop()
105 time.sleep(0.05)
106@@ -525,16 +528,14 @@
107 trans.progress = 101
108 last_pulse = time.time()
109 if proc.returncode != 0:
110- stdout = str(proc.stdout.read(),
111- # that can return "None", in this case, just
112- # assume something
113- sys.stdin.encoding or "UTF-8",
114- errors="replace")
115- #TRANSLATORS: The first %s is the key id and the second the server
116- raise TransactionFailed(ERROR_KEY_NOT_INSTALLED,
117- _("Failed to download and install the key "
118- "%s from %s:\n%s"),
119- keyid, keyserver, stdout)
120+ stdout = proc.stdout.read().decode('UTF-8', errors='replace')
121+ # FIXME: apt-key regularly exists with code 2 even on success
122+ if 'processed: 0' in stdout:
123+ #TRANSLATORS: The first %s is the key id and the second the server
124+ raise TransactionFailed(ERROR_KEY_NOT_INSTALLED,
125+ _("Failed to download and install the key "
126+ "%s from %s:\n%s"),
127+ keyid, keyserver, stdout)
128
129 def add_vendor_key_from_file(self, trans, path):
130 """Add the signing key from the given file to the trusted vendors.
131
132=== modified file 'tests/test_pk.py'
133--- tests/test_pk.py 2012-05-31 14:53:14 +0000
134+++ tests/test_pk.py 2012-05-31 16:44:20 +0000
135@@ -27,6 +27,13 @@
136
137 PY3K = sys.version_info.major > 2
138
139+if PY3K:
140+ from http.server import HTTPServer
141+ from http.server import SimpleHTTPRequestHandler as HTTPRequestHandler
142+else:
143+ from SimpleHTTPServer import BaseHTTPServer as HTTPServer
144+ from SimpleHTTPServer import SimpleHTTPRequestHandler as HTTPRequestHandler
145+
146
147 class PackageKitTest(aptdaemon.test.AptDaemonTestCase):
148
149@@ -45,6 +52,15 @@
150 self.orig_pythonpath = os.environ.get("PYTHONPATH")
151 os.environ["PYTHONPATH"] = "%s:%s" % (self.workdir,
152 os.environ.get("PYTHONPATH", ""))
153+ # write apt config for calling apt-key
154+ apt_conf = os.path.join(self.chroot.path, 'aptconfig')
155+ with open(apt_conf, 'w') as f:
156+ f.write('Dir "%s";\n' % self.chroot.path)
157+ os.environ['APT_CONFIG'] = apt_conf
158+
159+ # if tests install keys, have aptd query a local fake server
160+ os.environ['APTDAEMON_KEYSERVER'] = 'hkp://localhost:19191'
161+
162 self.start_session_aptd(self.chroot.path)
163 # Start the fake PolikcyKit daemon
164 self.start_fake_polkitd()
165@@ -388,6 +404,45 @@
166 self.assertTrue('format' in str(e), e)
167 self.assertTrue('http://example.com' in str(e), e)
168
169+ def test_install_signature(self):
170+ """Test installing a new GPG key"""
171+
172+ def apt_key_list():
173+ proc = subprocess.Popen(["apt-key", "list"],
174+ stdout=subprocess.PIPE, universal_newlines=True)
175+ out = proc.communicate()[0]
176+ self.assertEqual(proc.returncode, 0)
177+ return out
178+
179+ # we do not have any key initially
180+ self.assertEqual(apt_key_list().strip(), '')
181+
182+ # launch our keyserver
183+ self._start_keyserver()
184+ self.addCleanup(self._stop_keyserver)
185+
186+ # now add one
187+ client = pk.Client()
188+ res = client.install_signature(pk.SigTypeEnum.GPG, 'CF982D18', '', None,
189+ lambda p, t, d: True, None)
190+ self.assertEqual(res.get_exit_code(), pk.ExitEnum.SUCCESS)
191+
192+ # key was imported
193+ out = apt_key_list()
194+ self.assertTrue('CF982D18' in out, out)
195+
196+ def test_install_signature_error(self):
197+ """Test installing a new GPG key with failing server"""
198+
199+ # do not start keyserver, so http://localhost.. will not exist
200+ client = pk.Client()
201+ try:
202+ client.install_signature(pk.SigTypeEnum.GPG, '11111111', '', None,
203+ lambda p, t, d: True, None)
204+ except GLib.GError as e:
205+ self.assertTrue('Failed to download' in str(e), e)
206+ self.assertTrue('11111111' in str(e), e)
207+
208 def test_unimplemented(self):
209 """Test proper error message on unimplemented method."""
210 client = pk.Client()
211@@ -396,6 +451,35 @@
212 except GLib.GError as e:
213 self.assertTrue('implemented' in str(e))
214
215+ def _start_keyserver(self):
216+ '''Start a fake keyserver on http://localhost:19191'''
217+
218+ dir = os.path.join(self.chroot.path, 'pks')
219+ os.mkdir(dir, 0o700)
220+ os.symlink(os.path.join(REPO_PATH, 'glatzor.gpg'),
221+ os.path.join(dir, 'lookup'))
222+
223+ self.keyserver_pid = os.fork()
224+ if self.keyserver_pid == 0:
225+ # quiesce server log
226+ os.dup2(os.open('/dev/null', os.O_WRONLY), sys.stderr.fileno())
227+ os.chdir(self.chroot.path)
228+ httpd = HTTPServer.HTTPServer(('localhost', 19191), HTTPRequestHandler)
229+ httpd.serve_forever()
230+ os._exit(0)
231+
232+ # wait a bit until server is ready
233+ time.sleep(0.5)
234+
235+ def _stop_keyserver(self):
236+ '''Stop fake keyserver'''
237+
238+ assert self.keyserver_pid
239+
240+ os.kill(self.keyserver_pid, 15)
241+ os.wait()
242+ shutil.rmtree(os.path.join(self.chroot.path, 'pks'))
243+
244
245 @unittest.skipIf(sys.version_info.major < 3, "Python3 only")
246 def setUp():

Subscribers

People subscribed via source and target branches

to status/vote changes: